From 88112b96e829e0fdfb6ec12fd99fbdff7b08fe17 Mon Sep 17 00:00:00 2001 From: Jesse Mapel Date: Fri, 19 Feb 2021 13:16:23 -0700 Subject: [PATCH] Adds CSM camera model (#4291) * Initial CSMCamera model implementation. * Get CSM Camera working through a dummy setimage * More progress on SetImage * Cleanup. * Added resolution and radii to CSM camera model (#4261) * Added psuedoinverse method * Added CSM pixel resolution calculations * Add subsurfacepoint code and get working up to but not through setimage (#4264) Co-authored-by: Kristin Berry * Changed pseudoinverse computation to armadillo SVD based (#4263) * Changed psuedoinverse to Armadillo SVD * Fixed name * Fixed comparison warning * In progress set ground * Add SetUniversalGround for CSMCamera and nullptr initializations in Spice defaultinit Co-authored-by: Kristin Berry * Modified CSM setImage to intersect the shape model (#4266) * Fixed scaling issue in setImage * Added shape model intersection * added initial CSMCamera test (#4267) * added initial CSMCamera test * Added matchers and more tests * Added more set ground tests * minor clean up * Added proper ShapeModel stuff (#4269) * Added proper ShapeModel stuff * General clean-up * Starting to get campt working (#4270) * Added a bunch more stuff for campt (#4272) * Added a bunch of stuff for campt * Fixed compiler error * Got RA, DEC, and all Solar Calculations from campt correctly returning NULL (#4273) Co-authored-by: Kristin Berry * Updated CSMCamera tests to pass (#4278) * Updated CSMCamera tests * made another fixture * Moved dem radius to 1 place in the fixture * Added remaining function tests * Finished CSMCamera tests * Flipped illumination vector * Updated CameraFactory unitTest for new error message * Removed campt couts * Updated chip truth data for new camera factory error * Re-fixed CameraFactory truth * Changed Spice hasGroup to hasBlob for Target unit test * Update camera plugin loading and add a check to csminit that the camera can be instantiated. (#4279) * Update camera plugin loading and add a check to csminit that the camera model can be instantiated. * Update csminit to restore original label contents if csminit fails * Working on debugging an issue with saving and restoring the original csmstate blob if csminit fails * Updated StringBlob to be able to read in and then write out the same blob without doubling it * Update to use existing TargetName in label if not supplied by user. Co-authored-by: Kristin Berry * Added campt and hasblob tests (#4283) * Added campt and hasblob tests * Fixed spacing * Updated CSMCamera doc strings (#4282) * Updated doc strings * Added cmapt history * Fixed typo in CSMCamera::sunPosition docs * Get csminit tests passing (#4284) * Update tests * Get csminit tests passing Co-authored-by: Kristin Berry * Updated UTC time conversions (#4288) * Converted iTime test * Modified iTime::setUtc to accept basic formats * Update CSMCamera to use iTime::setUtc * Removed old include * docs clean-up * Update csminit to work with an input state and add associated tests. * Combined ISD vs State into single if else * Added changelog and histories * Removed duplicate error check * Fixed review comments * Added Sensor history comment Co-authored-by: Kristin Co-authored-by: Kristin Berry --- CHANGELOG.md | 1 + isis/src/base/apps/campt/campt.xml | 3 + isis/src/base/apps/csminit/csminit.cpp | 360 +++++-- isis/src/base/apps/csminit/csminit.xml | 28 +- isis/src/base/apps/spiceinit/spiceinit.cpp | 18 +- isis/src/base/objs/CSMCamera/CSMCamera.cpp | 945 ++++++++++++++++++ isis/src/base/objs/CSMCamera/CSMCamera.h | 144 +++ isis/src/base/objs/CSMCamera/Makefile | 7 + isis/src/base/objs/Camera/Camera.cpp | 91 +- isis/src/base/objs/Camera/Camera.h | 52 +- .../base/objs/CameraFactory/CameraFactory.cpp | 148 +-- .../base/objs/CameraFactory/CameraFactory.h | 10 +- .../objs/CameraFactory/CameraFactory.truth | 10 +- .../objs/CameraPointInfo/CameraPointInfo.cpp | 144 ++- isis/src/base/objs/Chip/Chip.truth | 4 +- isis/src/base/objs/Cube/Cube.cpp | 23 +- isis/src/base/objs/Cube/Cube.h | 2 + .../base/objs/LinearAlgebra/LinearAlgebra.cpp | 847 ++++++++-------- .../base/objs/LinearAlgebra/LinearAlgebra.h | 2 + isis/src/base/objs/Sensor/Sensor.cpp | 27 +- isis/src/base/objs/Sensor/Sensor.h | 49 +- isis/src/base/objs/Spice/Spice.cpp | 105 +- isis/src/base/objs/Spice/Spice.h | 86 +- isis/src/base/objs/StringBlob/StringBlob.cpp | 15 +- .../src/base/objs/SurfacePoint/SurfacePoint.h | 2 +- isis/src/base/objs/Target/Target.cpp | 141 ++- isis/src/base/objs/Target/Target.h | 7 + isis/src/base/objs/iTime/iTime.cpp | 31 +- isis/src/base/objs/iTime/iTime.h | 2 + isis/src/base/objs/iTime/iTime.truth | 100 -- isis/src/base/objs/iTime/unitTest.cpp | 296 ------ isis/tests/AlternativeTestCsmModel.cpp | 148 ++- isis/tests/AlternativeTestCsmModel.h | 83 +- isis/tests/CSMCameraTests.cpp | 466 +++++++++ isis/tests/CamptFunctionalTests.cpp | 57 ++ isis/tests/CubeTests.cpp | 8 + isis/tests/Fixtures.cpp | 127 +++ isis/tests/Fixtures.h | 45 +- isis/tests/FunctionalTestsCsminit.cpp | 167 +++- isis/tests/FunctionalTestsSpiceinit.cpp | 49 + isis/tests/LinearAlgebraTests.cpp | 80 ++ isis/tests/MockCsmPlugin.cpp | 261 +++++ isis/tests/MockCsmPlugin.h | 83 ++ isis/tests/Mocks.h | 98 +- isis/tests/TestCsmModel.cpp | 145 ++- isis/tests/TestCsmModel.h | 86 +- isis/tests/TestUtilities.cpp | 29 +- isis/tests/TestUtilities.h | 7 +- .../data/CSMCamera/mola_compressed_prep.cub | Bin 0 -> 75871 bytes isis/tests/iTimeTests.cpp | 167 ++++ 50 files changed, 4522 insertions(+), 1284 deletions(-) create mode 100644 isis/src/base/objs/CSMCamera/CSMCamera.cpp create mode 100644 isis/src/base/objs/CSMCamera/CSMCamera.h create mode 100644 isis/src/base/objs/CSMCamera/Makefile delete mode 100644 isis/src/base/objs/iTime/iTime.truth delete mode 100644 isis/src/base/objs/iTime/unitTest.cpp create mode 100644 isis/tests/CSMCameraTests.cpp create mode 100644 isis/tests/LinearAlgebraTests.cpp create mode 100644 isis/tests/MockCsmPlugin.cpp create mode 100644 isis/tests/MockCsmPlugin.h create mode 100644 isis/tests/data/CSMCamera/mola_compressed_prep.cub create mode 100644 isis/tests/iTimeTests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 94db79dead..eeaef4376f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ release. - Added the new csminit application and CSM Library loading to the IsisPreferences file. Together these allow users to get CSM state strings from ISD files. Once CSM camera model support is added, these will be used to setup a Cube to use a CSM camera model. - Added a new application, topds4, which generates an output PDS4 XML label and a PDS4-compliant ISIS Cube from an input Cube, a PDS4 label template, and optionally additional input XML, PVL, or JSON data. The Inja templating engine is used to render the output PDS4 label from the label template. [#4246](https://github.com/USGS-Astrogeology/ISIS3/pull/4246) +- Added the ability to use a Community Sensor Model (CSM) instead of an ISIS camera model. To use a CSM sensor model with a Cube run the csminit application on the Cube instead of spiceinit. ### Fixed diff --git a/isis/src/base/apps/campt/campt.xml b/isis/src/base/apps/campt/campt.xml index 484a68ec0f..8bde61137b 100644 --- a/isis/src/base/apps/campt/campt.xml +++ b/isis/src/base/apps/campt/campt.xml @@ -220,6 +220,9 @@ End_Group Removed units on look direction vectors to make them unitless. Fixes #5125. + + Modified to output Nulls when using a CSM Camera model and some values are not available. + diff --git a/isis/src/base/apps/csminit/csminit.cpp b/isis/src/base/apps/csminit/csminit.cpp index ac11dce07a..bc697f81fe 100644 --- a/isis/src/base/apps/csminit/csminit.cpp +++ b/isis/src/base/apps/csminit/csminit.cpp @@ -21,8 +21,10 @@ find files of those names at the top level of this repository. **/ #include "Blob.h" #include "Camera.h" #include "CameraFactory.h" +#include "CSMCamera.h" #include "Cube.h" #include "IException.h" +#include "ImagePolygon.h" #include "Process.h" #include "Pvl.h" #include "PvlGroup.h" @@ -46,101 +48,186 @@ namespace Isis { // Get the cube here so that we check early if it doesn't exist Cube *cube = p.SetInputCube(ui.GetFileName("FROM"), ui.GetInputAttribute("FROM"), ReadWrite); - // We have to call this to get the plugin list loaded right now - try { - Camera *cam = CameraFactory::Create(*cube); - delete cam; - } - catch(...) { - // Noop + // We have to call this to get the plugin list loaded. + CameraFactory::initPlugin(); + + // These three variables are the main product of the following if/else statement + QString pluginName; + QString modelName; + csm::Model *model = nullptr; + + if (ui.WasEntered("ISD") && ui.WasEntered("STATE")) { + QString message = "Cannot enter both [ISD] and [STATE]. Please enter either [ISD] or [STATE]."; + throw IException(IException::User, message, _FILEINFO_); } - QString isdFilePath = ui.GetFileName("ISD"); + else if (!ui.WasEntered("ISD") && !ui.WasEntered("STATE")) { + QString message = "Either an ISD or a State string must be entered."; + throw IException(IException::User, message, _FILEINFO_); + } - QList possibleModels; - for (const csm::Plugin * plugin : csm::Plugin::getList()) { - QString pluginName = QString::fromStdString(plugin->getPluginName()); - if (ui.WasEntered("PLUGINNAME") && pluginName != ui.GetString("PLUGINNAME")) { - continue; - } + else if (ui.WasEntered("ISD")) { + QString isdFilePath = ui.GetFileName("ISD"); - for (size_t modelIndex = 0; modelIndex < plugin->getNumModels(); modelIndex++) { - QString modelName = QString::fromStdString(plugin->getModelName(modelIndex)); - if (ui.WasEntered("MODELNAME") && modelName != ui.GetString("MODELNAME")) { + QList possibleModels; + for (const csm::Plugin * plugin : csm::Plugin::getList()) { + QString currentPluginName = QString::fromStdString(plugin->getPluginName()); + if (ui.WasEntered("PLUGINNAME") && currentPluginName != ui.GetString("PLUGINNAME")) { continue; } - csm::Isd fileIsd(isdFilePath.toStdString()); - if (plugin->canModelBeConstructedFromISD(fileIsd, modelName.toStdString())) { - QStringList modelSpec = {pluginName, modelName, QString::fromStdString(fileIsd.format())}; - possibleModels.append(modelSpec); - continue; // If the file ISD works, don't check the others + for (size_t modelIndex = 0; modelIndex < plugin->getNumModels(); modelIndex++) { + QString currentModelName = QString::fromStdString(plugin->getModelName(modelIndex)); + if (ui.WasEntered("MODELNAME") && currentModelName != ui.GetString("MODELNAME")) { + continue; + } + + csm::Isd fileIsd(isdFilePath.toStdString()); + if (plugin->canModelBeConstructedFromISD(fileIsd, currentModelName.toStdString())) { + QStringList modelSpec = { + currentPluginName, + currentModelName, + QString::fromStdString(fileIsd.format())}; + possibleModels.append(modelSpec); + continue; // If the file ISD works, don't check the other ISD formats + } + + csm::Nitf21Isd nitf21Isd(isdFilePath.toStdString()); + if (plugin->canModelBeConstructedFromISD(nitf21Isd, currentModelName.toStdString())) { + QStringList modelSpec = { + currentPluginName, + currentModelName, + QString::fromStdString(nitf21Isd.format())}; + possibleModels.append(modelSpec); + continue; // If the NITF 2.1 ISD works, don't check the other ISD formats + } } + } - csm::Nitf21Isd nitf21Isd(isdFilePath.toStdString()); - if (plugin->canModelBeConstructedFromISD(nitf21Isd, modelName.toStdString())) { - QStringList modelSpec = {pluginName, modelName, QString::fromStdString(nitf21Isd.format())}; - possibleModels.append(modelSpec); - continue; // If the NITF 2.1 ISD works, don't check the others + if (possibleModels.size() > 1) { + QString message = "Multiple models can be created from the ISD [" + isdFilePath + "]. " + "Re-run with the PLUGINNAME and MODELNAME parameters. " + "Possible plugin & model names:\n"; + for (const QStringList &modelSpec : possibleModels) { + message += "Plugin [" + modelSpec[0] + "], Model [" + modelSpec[1] + "]\n"; } + throw IException(IException::User, message, _FILEINFO_); } - } - if (possibleModels.size() > 1) { - QString message = "Multiple models can be created from the ISD [" + isdFilePath + "]. " - "Re-run with the PLUGINNAME and MODELNAME parameters. " - "Possible plugin & model names:\n"; - for (const QStringList &modelSpec : possibleModels) { - message += "Plugin [" + modelSpec[0] + "], Model [" + modelSpec[1] + "]\n"; + if (possibleModels.empty()) { + QString message = "No loaded model could be created from the ISD [" + isdFilePath + "]." + "Loaded plugin & model names:\n"; + for (const csm::Plugin * plugin : csm::Plugin::getList()) { + QString currentPluginName = QString::fromStdString(plugin->getPluginName()); + for (size_t modelIndex = 0; modelIndex < plugin->getNumModels(); modelIndex++) { + QString modelName = QString::fromStdString(plugin->getModelName(modelIndex)); + message += "Plugin [" + currentPluginName + "], Model [" + modelName + "]\n"; + } + } + throw IException(IException::User, message, _FILEINFO_); } - throw IException(IException::User, message, _FILEINFO_); - } - if (possibleModels.empty()) { - QString message = "No loaded model could be created from the ISD [" + isdFilePath + "]." - "Loaded plugin & model names:\n"; - for (const csm::Plugin * plugin : csm::Plugin::getList()) { - QString pluginName = QString::fromStdString(plugin->getPluginName()); - for (size_t modelIndex = 0; modelIndex < plugin->getNumModels(); modelIndex++) { - QString modelName = QString::fromStdString(plugin->getModelName(modelIndex)); - message += "Plugin [" + pluginName + "], Model [" + modelName + "]\n"; - } + // If we are here, then we have exactly 1 model + QStringList modelSpec = possibleModels.front(); + + if (modelSpec.size() != 3) { + QString message = "Model specification [" + modelSpec.join(" ") + "] has [" + modelSpec.size() + "] elements " + "when it should have 3 elements."; + throw IException(IException::Programmer, message, _FILEINFO_); } - throw IException(IException::User, message, _FILEINFO_); - } - // If we are here, then we have exactly 1 model - QStringList modelSpec = possibleModels.front(); - if (modelSpec.size() != 3) { - QString message = "Model specification [" + modelSpec.join(" ") + "] has [" + modelSpec.size() + "] elements " - "when it should have 3 elements."; - throw IException(IException::Programmer, message, _FILEINFO_); - } - const csm::Plugin *plugin = csm::Plugin::findPlugin(modelSpec[0].toStdString()); - csm::Model *model; - csm::Isd fileIsd(isdFilePath.toStdString()); - csm::Nitf21Isd nitf21Isd(isdFilePath.toStdString()); - if (modelSpec[2] == QString::fromStdString(fileIsd.format())) { - model = plugin->constructModelFromISD(fileIsd, modelSpec[1].toStdString()); - } - else if (modelSpec[2] == QString::fromStdString(nitf21Isd.format())) { - model = plugin->constructModelFromISD(nitf21Isd, modelSpec[1].toStdString()); + pluginName = modelSpec[0]; + modelName = modelSpec[1]; + QString isdFormat = modelSpec[2]; + + const csm::Plugin *plugin = csm::Plugin::findPlugin(pluginName.toStdString()); + if (plugin == NULL) { + QString message = "Cannot find requested Plugin: [" + pluginName + "]."; + throw IException(IException::User, message, _FILEINFO_); + } + + csm::Isd fileIsd(isdFilePath.toStdString()); + csm::Nitf21Isd nitf21Isd(isdFilePath.toStdString()); + if (isdFormat == QString::fromStdString(fileIsd.format())) { + model = plugin->constructModelFromISD(fileIsd, modelName.toStdString()); + } + else if (isdFormat == QString::fromStdString(nitf21Isd.format())) { + model = plugin->constructModelFromISD(nitf21Isd, modelName.toStdString()); + } + else { + QString message = "Invalid ISD format specifications [" + isdFormat + "]."; + throw IException(IException::Programmer, message, _FILEINFO_); + } + } // end of ISD if statement + + else if (ui.WasEntered("STATE")) { + FileName stateFilePath = ui.GetFileName("STATE"); + + std::ifstream file(stateFilePath.expanded().toStdString()); + std::stringstream buffer; + buffer << file.rdbuf(); + QString stateString = QString::fromStdString(buffer.str()); + + if (!ui.WasEntered("PLUGINNAME") && !ui.WasEntered("MODELNAME")) { + QString message = "When using a State string, PLUGINNAME and MODELNAME must be specified"; + throw IException(IException::Programmer, message, _FILEINFO_); + } + pluginName = ui.GetString("PLUGINNAME"); + modelName = ui.GetString("MODELNAME"); + + const csm::Plugin *plugin = csm::Plugin::findPlugin(pluginName.toStdString()); + if (plugin == NULL) { + QString message = "Cannot find requested Plugin: [" + pluginName + "]."; + throw IException(IException::User, message, _FILEINFO_); + } + + // TODO: Add warning argument and use message from csm::Warning for Isis::IException error. + if (plugin->canModelBeConstructedFromState(modelName.toStdString(), stateString.toStdString())){ + model = plugin->constructModelFromState(stateString.toStdString()); + } + else { + QString message = "Could not construct sensor model using STATE string and MODELNAME: [" + modelName + "]"; + throw IException(IException::Programmer, message, _FILEINFO_); + } + } // end of State else statement + + string modelState = model->getModelState(); + + // Making copies of original Pvl Groups from input label so they can be restored if csminit fails. + PvlGroup originalInstrument; + PvlGroup originalKernels; + PvlGroup originalCsmInfo; + if (cube->hasGroup("Instrument")) { + originalInstrument = cube->group("Instrument"); } - else { - QString message = "Invalid ISD format specifications [" + modelSpec[2] + "]."; - throw IException(IException::Programmer, message, _FILEINFO_); + + if (cube->hasGroup("Kernels")) { + originalKernels = cube->group("Kernels"); } - string modelState = model->getModelState(); + if (cube->hasGroup("CsmInfo")) { + originalCsmInfo = cube->group("CsmInfo"); + } - // Add the TargetName to the instrument group, if specified: + if (!cube->hasGroup("Instrument")) { + cube->putGroup(PvlGroup("Instrument")); + } + PvlGroup &instrumentGroup = cube->group("Instrument"); if (ui.WasEntered("TARGETNAME")) { - if (!cube->hasGroup("Instrument")) { - cube->putGroup(PvlGroup("Instrument")); - } - PvlGroup &instrumentGroup = cube->group("Instrument"); instrumentGroup.addKeyword(PvlKeyword("TargetName", ui.GetString("TARGETNAME")), Pvl::Replace); } + // If the user doesn't specify a target name, then we will still need + // something on the label for the Target & ShapeModel so add Unknown + else if (!instrumentGroup.hasKeyword("TargetName")) { + PvlKeyword targetKey("TargetName", "Unknown"); + targetKey.addComment("Radii will come from the CSM model"); + instrumentGroup.addKeyword(targetKey, Pvl::Replace); + } + + if (!instrumentGroup.hasKeyword("InstrumentId")) { + PvlKeyword instrumentIdKey("InstrumentId", QString::fromStdString(model->getSensorIdentifier())); + instrumentGroup.addKeyword(instrumentIdKey, Pvl::Replace); + } // Populate the CsmInfo group with useful information cube->deleteGroup("CsmInfo"); @@ -152,6 +239,7 @@ namespace Isis { infoGroup += PvlKeyword("ReferenceTime", QString::fromStdString(model->getReferenceDateAndTime())); csm::GeometricModel *modelWithParams = dynamic_cast(model); + if (modelWithParams) { PvlKeyword paramNames("ModelParameterNames"); PvlKeyword paramUnits("ModelParameterUnits"); @@ -167,15 +255,15 @@ namespace Isis { case csm::param::FICTITIOUS: paramTypes += "FICTITIOUS"; break; - + case csm::param::REAL: paramTypes += "REAL"; break; - + case csm::param::FIXED: paramTypes += "FIXED"; break; - + default: paramTypes += "UNKNOWN"; break; @@ -196,10 +284,10 @@ namespace Isis { if (ui.WasEntered("SHAPEMODEL")) { // TODO validate the shapemodel - kernelsGroup.addKeyword(PvlKeyword("ShapeModel", ui.GetString("SHAPEMODEL")), Pvl::Replace); + kernelsGroup.addKeyword(PvlKeyword("ShapeModel", ui.GetFileName("SHAPEMODEL")), Pvl::Replace); } else { - kernelsGroup.addKeyword(PvlKeyword("ShapeModel", "Ellipsoid"), Pvl::Replace); + kernelsGroup.addKeyword(PvlKeyword("ShapeModel", "Null"), Pvl::Replace); } // Get rid of keywords from spiceinit @@ -270,33 +358,119 @@ namespace Isis { kernelsGroup.deleteKeyword("Tolerance"); } - // Remove tables from spiceinit - cube->deleteBlob("Table", "InstrumentPointing"); - cube->deleteBlob("Table", "InstrumentPosition"); - cube->deleteBlob("Table", "BodyRotation"); - cube->deleteBlob("Table", "SunPosition"); - cube->deleteBlob("Table", "CameraStatistics"); - cube->deleteBlob("Polygon", "Footprint"); if (cube->label()->hasObject("NaifKeywords")) { cube->label()->deleteObject("NaifKeywords"); } + // Save off all old Blobs to restore in the case of csminit failure + StringBlob originalCsmStateBlob("", "CSMState"); + if (cube->hasBlob("String", "CSMState")) { + cube->read(originalCsmStateBlob); + } + + Table originalInstrumentPointing("InstrumentPointing"); + if (cube->hasTable("InstrumentPointing")) { + cube->read(originalInstrumentPointing); + } + + Table originalInstrumentPosition("InstrumentPosition"); + if (cube->hasTable("InstrumentPosition")) { + cube->read(originalInstrumentPosition); + } + + Table originalBodyRotation("BodyRotation"); + if (cube->hasTable("BodyRotation")) { + cube->read(originalBodyRotation); + } + + Table originalSunPosition("SunPosition"); + if (cube->hasTable("SunPosition")) { + cube->read(originalSunPosition); + } + + Table originalCameraStatistics("CameraStatistics"); + if (cube->hasTable("CameraStatistics")) { + cube->read(originalCameraStatistics); + } + + ImagePolygon originalFootprint; + if (cube->hasBlob("Polygon", "ImageFootprint")) { + cube->read(originalFootprint); + } + + // Remove blob from old csminit run cube->deleteBlob("String", "CSMState"); - // Create our CSM State blob as a string - // Add the CSM string to the Blob. + // Remove tables from spiceinit before writing to the cube + cube->deleteBlob("Table", "InstrumentPointing"); + cube->deleteBlob("Table", "InstrumentPosition"); + cube->deleteBlob("Table", "BodyRotation"); + cube->deleteBlob("Table", "SunPosition"); + cube->deleteBlob("Table", "CameraStatistics"); + cube->deleteBlob("Polygon", "Footprint"); + + // Create our CSM State blob as a string and add the CSM string to the Blob. StringBlob csmStateBlob(modelState, "CSMState"); PvlObject &blobLabel = csmStateBlob.Label(); - blobLabel += PvlKeyword("ModelName", QString::fromStdString(model->getModelName())); - blobLabel += PvlKeyword("PluginName", QString::fromStdString(plugin->getPluginName())); - - // Write CSM State blob to cube + blobLabel += PvlKeyword("ModelName", modelName); + blobLabel += PvlKeyword("PluginName", pluginName); cube->write(csmStateBlob); - // TODO attempt to get the CSM Model from the cube + try { + CameraFactory::Create(*cube); + p.WriteHistory(*cube); + } + catch (IException &e) { + // Restore the original groups on the label + cube->deleteGroup("Instrument"); + if (originalInstrument.keywords() != 0) { + cube->putGroup(originalInstrument); + } - p.WriteHistory(*cube); - } + cube->deleteGroup("Kernels"); + if (originalKernels.keywords() != 0) { + cube->putGroup(originalKernels); + } + + cube->deleteGroup("CsmInfo"); + if (originalCsmInfo.keywords() != 0) { + cube->putGroup(originalCsmInfo); + } + + cube->deleteBlob("String", "CSMState"); + + // Restore the original blobs + if (originalCsmStateBlob.Size() != 0) { + cube->write(originalCsmStateBlob); + } + + if (originalInstrumentPointing.Records() != 0) { + cube->write(originalInstrumentPointing); + } + + if (originalInstrumentPosition.Records() != 0) { + cube->write(originalInstrumentPosition); + } + + if (originalBodyRotation.Records() != 0) { + cube->write(originalBodyRotation); + } + + if (originalSunPosition.Records() != 0) { + cube->write(originalSunPosition); + } + if (originalCameraStatistics.Records() != 0) { + cube->write(originalCameraStatistics); + } + + if (originalFootprint.Size() != 0) { + cube->write(originalFootprint); + } + + QString message = "Failed to create a CSMCamera."; + throw IException(e, IException::Unknown, message, _FILEINFO_); + } + } } diff --git a/isis/src/base/apps/csminit/csminit.xml b/isis/src/base/apps/csminit/csminit.xml index 037ae3155b..bcddb1d95c 100644 --- a/isis/src/base/apps/csminit/csminit.xml +++ b/isis/src/base/apps/csminit/csminit.xml @@ -40,6 +40,15 @@ Original Version + + Modified to always add a TargetName and Instrument group to the label for the camera model. + + + Modified to revert label and Cube changes on failure. + + + Added the ability to take a state string instead of an ISD. + @@ -61,6 +70,7 @@ filename input + none The Instrument Support Data (ISD) file that will be used. @@ -69,18 +79,30 @@ model state string. The ISD can be just a filename ISD or a NITF2.1 ISD. + + + filename + none + input + + A Community Sensor Model (CSM) state string + + + A file containing a Community Sensor Model (CSM) state string that will be used to create the + model for the input cube. + + string - none + Current TargetName in input label or Unknown if not present. The name of the body observed by the image. - The name of the body observed by the image. If not entered, then the - target already listed on the label will be used. + The name of the body observed by the image. diff --git a/isis/src/base/apps/spiceinit/spiceinit.cpp b/isis/src/base/apps/spiceinit/spiceinit.cpp index c1a8f6dd28..f6b3245dc6 100644 --- a/isis/src/base/apps/spiceinit/spiceinit.cpp +++ b/isis/src/base/apps/spiceinit/spiceinit.cpp @@ -17,6 +17,7 @@ #include "PvlToPvlTranslationManager.h" #include "SpiceClient.h" #include "SpiceClientStarter.h" +#include "StringBlob.h" #include "Table.h" #include "UserInterface.h" #include "spiceinit.h" @@ -262,7 +263,7 @@ namespace Isis { } } icube->deleteGroup("CsmInfo"); - icube->deleteBlob("String","CSMState"); + p.WriteHistory(*icube); p.EndProcess(); } @@ -377,6 +378,15 @@ namespace Isis { currentKernels.addKeyword(iakKeyword, Pvl::Replace); currentKernels.addKeyword(demKeyword, Pvl::Replace); + // Save off the CSM State so it can be restored if spiceinit fails + StringBlob csmState("", "CSMState"); + if (icube->hasBlob("String", "CSMState")) { + icube->read(csmState); + } + + // Delete the CSM State blob so that CameraFactory doesn't try to instantiate a CSMCamera + icube->deleteBlob("String", "CSMState"); + // report qualities PvlKeyword spkQuality("InstrumentPositionQuality"); spkQuality.addValue(Kernel::typeEnum(spk.type())); @@ -478,6 +488,10 @@ namespace Isis { log->addGroup(currentKernels); } icube->putGroup(originalKernels); + + // restore CSM State blob if spiceinit failed + icube->write(csmState); + throw IException(e); } @@ -553,7 +567,7 @@ namespace Isis { *icube->label() += cam->getStoredNaifKeywords(); } - //modify Kernels group only + // Modify Kernels group only else { Pvl *label = icube->label(); int i = 0; diff --git a/isis/src/base/objs/CSMCamera/CSMCamera.cpp b/isis/src/base/objs/CSMCamera/CSMCamera.cpp new file mode 100644 index 0000000000..f649241331 --- /dev/null +++ b/isis/src/base/objs/CSMCamera/CSMCamera.cpp @@ -0,0 +1,945 @@ +/** This is free and unencumbered software released into the public domain. + +The authors of ISIS do not claim copyright on the contents of this file. +For more details about the LICENSE terms and the AUTHORS, you will +find files of those names at the top level of this repository. **/ + +/* SPDX-License-Identifier: CC0-1.0 */ + +#include "CSMCamera.h" +#include "CameraGroundMap.h" +#include "CameraSkyMap.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "CameraDetectorMap.h" +#include "CameraDistortionMap.h" +#include "CameraFocalPlaneMap.h" +#include "Constants.h" +#include "FileName.h" +#include "IException.h" +#include "IString.h" +#include "iTime.h" +#include "Latitude.h" +#include "Longitude.h" +#include "LinearAlgebra.h" +#include "NaifStatus.h" +#include "SpecialPixel.h" +#include "StringBlob.h" + +#include "csm/Warning.h" +#include "csm/Error.h" +#include "csm/Plugin.h" +#include "csm/Ellipsoid.h" +#include "csm/SettableEllipsoid.h" + +using namespace std; + +namespace Isis { + + /** + * Constructor for an ISIS Camera model that uses a Community Sensor Model (CSM) + * for the principal transformations. + * + * @param cube The Cube containing image data and CSM Model information for the + * ISIS Camera Model. + */ + CSMCamera::CSMCamera(Cube &cube) : Camera(cube) { + StringBlob state("","CSMState"); + cube.read(state); + PvlObject &blobLabel = state.Label(); + QString pluginName = blobLabel.findKeyword("PluginName")[0]; + QString modelName = blobLabel.findKeyword("ModelName")[0]; + init(cube, pluginName, modelName, QString::fromStdString(state.string())); + } + + + /** + * Init method which performs most of the setup for the CSM Camera Model inside ISIS. + * + * @param cube The cube with the image data + * @param pluginName The name of the CSM::Plugin that will create the CSM::Model + * @param modelName The name of the CSM::Model that will be created + * @param stateString The state string the the CSM::Model will be created from + */ + void CSMCamera::init(Cube &cube, QString pluginName, QString modelName, QString stateString){ + const csm::Plugin *plugin = csm::Plugin::findPlugin(pluginName.toStdString()); + if (!plugin) { + QStringList availablePlugins; + for (const csm::Plugin *plugin: csm::Plugin::getList()) { + availablePlugins.append(QString::fromStdString(plugin->getPluginName())); + } + QString msg = "Failed to find plugin [" + pluginName + "] for image [" + cube.fileName() + + "]. Check that the corresponding CSM plugin library is in the directory " + "specified by your IsisPreferences. Loaded plugins [" + + availablePlugins.join(", ") + "]."; + throw IException(IException::User, msg, _FILEINFO_); + } + if (!plugin->canModelBeConstructedFromState(modelName.toStdString(), stateString.toStdString())) { + QString msg = "CSM state string attached to image [" + cube.fileName() + "]. cannot " + "be converted to a [" + modelName + "] using [" + pluginName + "]."; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + m_model = dynamic_cast(plugin->constructModelFromState(stateString.toStdString())); + // If the dynamic cast failed, raise an exception + if (!m_model) { + QString msg = "Failed to convert CSM Model to RasterGM."; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + m_instrumentNameLong = QString::fromStdString(m_model->getSensorIdentifier()); + m_instrumentNameShort = QString::fromStdString(m_model->getSensorIdentifier()); + m_spacecraftNameLong = QString::fromStdString(m_model->getPlatformIdentifier()); + m_spacecraftNameShort = QString::fromStdString(m_model->getPlatformIdentifier()); + + QString timeString = QString::fromStdString(m_model->getReferenceDateAndTime()); + // Strip the UTC time zone indicator for iTime + timeString.remove("Z"); + m_refTime.setUtc(timeString); + + setTarget(*cube.label()); + } + + + /** + * Set the image sample and line for the Camera Model and then compute the + * corresponding image time, look vector, and ground point. + * + * @param sample The image sample coordinate + * @param line The image line coordinate + * + * @returns @b bool If the image coordinate was set successfully + */ + bool CSMCamera::SetImage(const double sample, const double line) { + csm::ImageCoord imagePt; + isisToCsmPixel(p_alphaCube->AlphaLine(line), p_alphaCube->AlphaSample(sample), imagePt); + double achievedPrecision = 0; + csm::WarningList warnings; + + csm::EcefLocus imageLocus; + try { + imageLocus = m_model->imageToRemoteImagingLocus(imagePt, + 0.001, + &achievedPrecision, + &warnings); + } + catch (csm::Error &e) { + return false; + } + + // Check for issues on the CSM end + if (achievedPrecision > 0.001) { + return false; + } + if (!warnings.empty()) { + for (csm::Warning warning : warnings) { + if (warning.getWarning() == csm::Warning::IMAGE_COORD_OUT_OF_BOUNDS){ + return false; + } + } + } + + // ISIS sensors work in Kilometers, CSM works in meters + std::vector obsPosition = {imageLocus.point.x / 1000.0, + imageLocus.point.y / 1000.0, + imageLocus.point.z / 1000.0}; + std::vector locusVec = {imageLocus.direction.x, + imageLocus.direction.y, + imageLocus.direction.z}; + if(!target()->shape()->intersectSurface(obsPosition, locusVec)) { + return false; + } + + // If we are here then everything went well so save the pixel and return true + m_lookB[0] = locusVec[0]; + m_lookB[1] = locusVec[1]; + m_lookB[2] = locusVec[2]; + m_newLookB = true; + p_pointComputed = true; + p_childSample = sample; + p_childLine = line; + if (!m_et) { + m_et = new iTime(); + } + *m_et = m_refTime + m_model->getImageTime(imagePt); + return true; + } + + + /** + * Set the latitude and longitude for the Camera Model and then compute the + * corresponding image time, look vector, and image coordinate. The ground + * point radius will be computed from the shape model. + * + * @param latitude The ground point latitude in degrees + * @param longitude The ground point longitude in positive East, 360 domain degrees + * + * @returns @b bool If the ground point was set successfully + */ + bool CSMCamera::SetUniversalGround(const double latitude, const double longitude) { + return SetGround( + Latitude(latitude, Angle::Degrees), + Longitude(longitude, Angle::Degrees)); + } + + +/** + * Set the latitude, longitude, and radius for the Camera Model and then compute the + * corresponding image time, look vector, and image coordinate. + * + * @param latitude The ground point latitude in degrees + * @param longitude The ground point longitude in positive East, 360 domain degrees + * @param radius The ground point radius in meters + * + * @returns @b bool If the ground point was set successfully + */ + bool CSMCamera::SetUniversalGround(const double latitude, const double longitude, double radius) { + return SetGround(SurfacePoint( + Latitude(latitude, Angle::Degrees), + Longitude(longitude, Angle::Degrees), + Distance(radius, Distance::Meters))); + } + + + /** + * Set the latitude and longitude for the Camera Model and then compute the + * corresponding image time, look vector, and image coordinate. The ground + * point radius will be computed from the shape model. + * + * @param latitude The ground point latitude + * @param longitude The ground point longitude + * + * @returns @b bool If the ground point was set successfully + */ + bool CSMCamera::SetGround(Latitude latitude, Longitude longitude) { + ShapeModel *shape = target()->shape(); + Distance localRadius; + + if (shape->name() != "Plane") { // this is the normal behavior + localRadius = LocalRadius(latitude, longitude); + } + else { + localRadius = Distance(latitude.degrees(),Distance::Kilometers); + latitude = Latitude(0.,Angle::Degrees); + } + + if (!localRadius.isValid()) { + target()->shape()->clearSurfacePoint(); + return false; + } + + return SetGround(SurfacePoint(latitude, longitude, localRadius)); + } + + + /** + * Set the ground point for the Camera Model and then compute the + * corresponding image time, look vector, and image coordinate. + * + * @param surfacePt The ground point + * + * @returns @b bool If the ground point was set successfully + */ + bool CSMCamera::SetGround(const SurfacePoint & surfacePt) { + ShapeModel *shape = target()->shape(); + if (!surfacePt.Valid()) { + shape->clearSurfacePoint(); + return false; + } + + bool validBackProject = true; + + // Back project through the CSM model + csm::ImageCoord imagePt; + double achievedPrecision = 0; + csm::WarningList warnings; + csm::EcefCoord groundPt = isisToCsmGround(surfacePt); + try { + imagePt = m_model->groundToImage(groundPt, 0.01, &achievedPrecision, &warnings); + } + catch (csm::Error &e) { + validBackProject = false; + } + if (achievedPrecision > 0.01) { + validBackProject = false; + } + if (!warnings.empty()) { + for (csm::Warning warning : warnings) { + if (warning.getWarning() == csm::Warning::IMAGE_COORD_OUT_OF_BOUNDS){ + validBackProject = false; + } + } + } + + // Check for occlusion + double line, sample; + csmToIsisPixel(imagePt, line, sample); + csm::EcefLocus imageLocus = m_model->imageToRemoteImagingLocus(imagePt); + std::vector sensorPosition = {imageLocus.point.x, imageLocus.point.y, imageLocus.point.z}; + shape->clearSurfacePoint(); + shape->intersectSurface(surfacePt, + sensorPosition, + true); + if (!shape->hasIntersection()) { + validBackProject = false; + } + + // If the back projection was successful, then save it + if (validBackProject) { + m_lookB[0] = imageLocus.direction.x; + m_lookB[1] = imageLocus.direction.y; + m_lookB[2] = imageLocus.direction.z; + m_newLookB = true; + p_childSample = p_alphaCube->BetaSample(sample); + p_childLine = p_alphaCube->BetaLine(line); + p_pointComputed = true; + shape->setHasIntersection(true); + if (!m_et) { + m_et = new iTime(); + } + *m_et = m_refTime + m_model->getImageTime(imagePt); + return true; + } + + // Otherwise reset + shape->clearSurfacePoint(); + return false; + } + + + /** + * Compute the line resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so this uses a more time consuming but + * more accurate method and thus is equivalent to the oblique line resolution. + * + * For time dependent sensor models, this may also be the line-to-line resolution + * and not the resolution within a line or framelet. This is determined by the + * CSM model's ground computeGroundPartials method. + * + * @returns @b double The line resolution in meters per pixel + */ + double CSMCamera::LineResolution() { + vector imagePartials = ImagePartials(); + return sqrt(imagePartials[0]*imagePartials[0] + + imagePartials[2]*imagePartials[2] + + imagePartials[4]*imagePartials[4]); + } + + + /** + * Compute the sample resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so this uses a more time consuming but + * more accurate method and thus is equivalent to the oblique sample resolution. + * + * @returns @b double The sample resolution in meters per pixel + */ + double CSMCamera::SampleResolution() { + vector imagePartials = ImagePartials(); + return sqrt(imagePartials[1]*imagePartials[1] + + imagePartials[3]*imagePartials[3] + + imagePartials[5]*imagePartials[5]); + } + + + /** + * Compute the detector resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so this uses a more time consuming but + * more accurate method and thus is equivalent to the oblique detector resolution. + * + * @returns @b double The detector resolution in meters per pixel + */ + double CSMCamera::DetectorResolution() { + // Redo the line and sample resolution calculations because it avoids + // a call to ImagePartials which could be a costly call + vector imagePartials = ImagePartials(); + double lineRes = sqrt(imagePartials[0]*imagePartials[0] + + imagePartials[2]*imagePartials[2] + + imagePartials[4]*imagePartials[4]); + double sampRes = sqrt(imagePartials[1]*imagePartials[1] + + imagePartials[3]*imagePartials[3] + + imagePartials[5]*imagePartials[5]); + return (sampRes + lineRes) / 2.0; + } + + + /** + * Compute the oblique line resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so obliqueness does not need to be + * accounted for. Thus, this is equivalent to the line resolution. + * + * @returns @b double The oblique line resolution in meters per pixel + */ + double CSMCamera::ObliqueLineResolution() { + // CSM resolution is always the oblique resolution so just return it + return LineResolution(); + } + + + /** + * Compute the oblique sample resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so obliqueness does not need to be + * accounted for. Thus, this is equivalent to the sample resolution. + * + * @returns @b double The oblique sample resolution in meters per pixel + */ + double CSMCamera::ObliqueSampleResolution() { + // CSM resolution is always the oblique resolution so just return it + return SampleResolution(); + } + + + /** + * Compute the oblique detector resolution in meters per pixel for the current set point. + * + * CSM sensor models do not expose all of the necessary parameters to do the + * same calculation as ISIS sensor models, so obliqueness does not need to be + * accounted for. Thus, this is equivalent to the detector resolution. + * + * @returns @b double The oblique detector resolution in meters per pixel + */ + double CSMCamera::ObliqueDetectorResolution() { + // CSM resolution is always the oblique resolution so just return it + return DetectorResolution(); + } + + + /** + * Returns the currently set parent line for the camera model. + * This is the line from the original image before any cropping, scaling, or + * other transformations. + * + * @returns @b double The currently set line + */ + double CSMCamera::parentLine() const { + return p_alphaCube->AlphaLine(Line()); + } + + + /** + * Returns the currently set parent sample for the camera model. + * This is the sample from the original image before any cropping, scaling, or + * other transformations. + * + * @returns @b double The currently set sample + */ + double CSMCamera::parentSample() const { + return p_alphaCube->AlphaSample(Sample()); + } + + + /** + * Get the position of the sensor in the body fixed coordinate system at the + * currently set time. + * + * @param[out] p A double array that will be filled with the (X, Y, Z) + * position in kilometers. + */ + void CSMCamera::instrumentBodyFixedPosition(double p[3]) const { + std::vector position = sensorPositionBodyFixed(); + p[0] = position[0]; + p[1] = position[1]; + p[2] = position[2]; + } + + + /** + * Get the position of the sensor in the body fixed coordinate system at the + * currently set time. + * + * @returns @b std::vector The (X, Y, Z) position in kilometers. + */ + std::vector CSMCamera::sensorPositionBodyFixed() const { + return sensorPositionBodyFixed(parentLine(), parentSample()); + } + + + /** + * Get the position of the sensor in the body fixed coordinate system at an + * image coordinate + * + * @param line The line of the image coordinate + * @param sample the sample of the image coordinate + * + * @returns @b std::vector The (X, Y, Z) position in kilometers. + */ + std::vector CSMCamera::sensorPositionBodyFixed(double line, double sample) const { + csm::ImageCoord imagePt; + isisToCsmPixel(line, sample, imagePt); + csm::EcefCoord sensorPosition = m_model->getSensorPosition(imagePt); + // CSM uses meters, but ISIS wants this in Km + std::vector result { + sensorPosition.x / 1000.0, + sensorPosition.y / 1000.0, + sensorPosition.z / 1000.0}; + return result; + } + + + /** + * Get the latitude and longitude of the sub-spacecraft point at the currently + * set time. + * + * @param[out] lat Will be filled with the latitude in degrees + * @param[out] lon Will be filled with the longitude in positive East, + * 360 domain degrees + */ + void CSMCamera::subSpacecraftPoint(double &lat, double &lon) { + subSpacecraftPoint(lat, lon, parentLine(), parentSample()); + } + + + /** + * Get the latitude and longitude of the sub-spacecraft point at the an image + * coordinate. + * + * @param[out] lat Will be filled with the latitude in degrees + * @param[out] lon Will be filled with the longitude in positive East, + * 360 domain degrees + * @param line The line of the image coordinate + * @param sample the sample of the image coordinate + */ + void CSMCamera::subSpacecraftPoint(double &lat, double &lon, double line, double sample) { + // Get s/c position from CSM because it is vector from center of body to that + vector sensorPosition = sensorPositionBodyFixed(line, sample); + SurfacePoint surfacePoint( + Displacement(sensorPosition[0], Displacement::Kilometers), + Displacement(sensorPosition[1], Displacement::Kilometers), + Displacement(sensorPosition[2], Displacement::Kilometers)); + lat = surfacePoint.GetLatitude().degrees(); + lon = surfacePoint.GetLongitude().degrees(); + } + + + /** + * Compute the partial derivatives of the ground point with respect to + * the line and sample at the current ground point. + * + * The resultant partials are + * x WRT line + * x WRT sample + * y WRT line + * y WRT sample + * z WRT line + * z WRT sample + * + * @return @b std::vector The partial derivatives of the image to ground + * transformation + */ + vector CSMCamera::ImagePartials() { + return ImagePartials(GetSurfacePoint()); + } + + + /** + * Compute the partial derivatives of the ground point with respect to + * the line and sample at a ground point. + * + * The resultant partials are + * x WRT line + * x WRT sample + * y WRT line + * y WRT sample + * z WRT line + * z WRT sample + * + * These are not normally available from the CSM model, so we use + * csm::RasterGM::computeGroundPartials to get the Jacobian of the ground to + * image transformation. Then we use the pseudoinverse of that to get the + * Jacobian of the image to ground transformation. + * + * @param groundPoint The ground point to compute the partials at + * + * @return @b std::vector The partial derivatives of the image to ground + * transformation + */ + vector CSMCamera::ImagePartials(SurfacePoint groundPoint) { + csm::EcefCoord groundCoord = isisToCsmGround(groundPoint); + vector groundPartials = m_model->computeGroundPartials(groundCoord); + + // Jacobian format is + // line WRT X line WRT Y line WRT Z + // samp WRT X samp WRT Y samp WRT Z + LinearAlgebra::Matrix groundMatrix(2, 3); + groundMatrix(0,0) = groundPartials[0]; + groundMatrix(0,1) = groundPartials[1]; + groundMatrix(0,2) = groundPartials[2]; + groundMatrix(1,0) = groundPartials[3]; + groundMatrix(1,1) = groundPartials[4]; + groundMatrix(1,2) = groundPartials[5]; + + LinearAlgebra::Matrix imageMatrix = LinearAlgebra::pseudoinverse(groundMatrix); + + vector imagePartials = {imageMatrix(0,0), + imageMatrix(0,1), + imageMatrix(1,0), + imageMatrix(1,1), + imageMatrix(2,0), + imageMatrix(2,1)}; + return imagePartials; + } + + + /** + * Set the Target object for the camera model. + * + * @param label The label containing information to create the Target from + */ + void CSMCamera::setTarget(Pvl label) { + Target *target = new Target(label); + + // get radii from CSM + csm::Ellipsoid targetEllipsoid = csm::SettableEllipsoid::getEllipsoid(m_model); + std::vector radii = {Distance(targetEllipsoid.getSemiMajorRadius(), Distance::Meters), + Distance(targetEllipsoid.getSemiMajorRadius(), Distance::Meters), + Distance(targetEllipsoid.getSemiMinorRadius(), Distance::Meters)}; + target->setRadii(radii); + + // Target needs to be able to access the camera to do things like + // compute resolution + target->setSpice(this); + + if (m_target) { + delete m_target; + m_target = nullptr; + } + + m_target = target; + } + + + /** + * Convert an ISIS pixel coordinate to a CSM pixel coordinate. + * The ISIS image origin is (0.5, 0.5), the CSM image origin is (0, 0). This + * function accounts for that and wraps the coordinate in a csm::ImageCoord. + * + * @param line The ISIS line of the image coordinate + * @param sample The ISIS sample of the image coordinate + * @param[out] csmPixel The CSM image coordinate + */ + void CSMCamera::isisToCsmPixel(double line, double sample, csm::ImageCoord &csmPixel) const { + csmPixel.line = line - 0.5; + csmPixel.samp = sample - 0.5; + } + + + /** + * Convert a CSM pixel coordinate to an ISIS pixel coordinate. + * The ISIS image origin is (0.5, 0.5), the CSM image origin is (0, 0). This + * function accounts for that and unpacks the csm::ImageCoord. + * + * @param csmPixel The CSM image coordinate + * @param[out] line The ISIS line of the image coordinate + * @param[out] sample The ISIS sample of the image coordinate + */ + void CSMCamera::csmToIsisPixel(csm::ImageCoord csmPixel, double &line, double &sample) const { + line = csmPixel.line + 0.5; + sample = csmPixel.samp + 0.5; + } + + + /** + * Convert an ISIS ground point into a CSM ground point. + * ISIS ground points can be created from and converted to many different + * units and coordinate systems. CSM ground points are always rectangular, + * body-fixed coordinates in meters. + * + * @param groundPt The ISIS ground coordinate + * + * @returns @b csm::EcefCoord the CSM ground coordinate in meters + */ + csm::EcefCoord CSMCamera::isisToCsmGround(const SurfacePoint &groundPt) const { + return csm::EcefCoord(groundPt.GetX().meters(), + groundPt.GetY().meters(), + groundPt.GetZ().meters()); + } + + + /** + * Convert a CSM ground point into an ISIS ground point. + * ISIS ground points can be created from and converted to many different + * units and coordinate systems. CSM ground points are always rectangular, + * body-fixed coordinates in meters. + * + * @param groundPt The CSM ground coordinate in meters + * + * @returns @b SurfacePointthe ISIS ground coordinate + */ + SurfacePoint CSMCamera::csmToIsisGround(const csm::EcefCoord &groundPt) const { + return SurfacePoint(Displacement(groundPt.x, Displacement::Meters), + Displacement(groundPt.y, Displacement::Meters), + Displacement(groundPt.z, Displacement::Meters)); + } + + + /** + * Compute the phase angle at the currently set ground point. + * + * @returns @b double The phase angle in degrees + */ + double CSMCamera::PhaseAngle() const { + csm::EcefCoord groundPt = isisToCsmGround(GetSurfacePoint()); + csm::EcefVector sunEcefVec = m_model->getIlluminationDirection(groundPt); + // ISIS wants the position of the sun, not just the vector from the ground + // point to the sun. So, we approximate this by adding in the ground point. + // ISIS wants this in Km so convert + std::vector sunVec = { + (groundPt.x - sunEcefVec.x) / 1000.0, + (groundPt.y - sunEcefVec.y) / 1000.0, + (groundPt.z - sunEcefVec.z) / 1000.0}; + return target()->shape()->phaseAngle(sensorPositionBodyFixed(), sunVec); + } + + + /** + * Compute the emission angle at the currently set ground point. + * + * @returns @b double The emission angle in degrees + */ + double CSMCamera::EmissionAngle() const { + return target()->shape()->emissionAngle(sensorPositionBodyFixed()); + } + + + /** + * Compute the incidence angle at the currently set ground point. + * + * @returns @b double The incidence angle in degrees + */ + double CSMCamera::IncidenceAngle() const { + csm::EcefCoord groundPt = isisToCsmGround(GetSurfacePoint()); + csm::EcefVector sunEcefVec = m_model->getIlluminationDirection(groundPt); + // ISIS wants the position of the sun, not just the vector from the ground + // point to the sun. So, we approximate this by adding in the ground point. + // ISIS wants this in Km so convert + std::vector sunVec = { + (groundPt.x - sunEcefVec.x) / 1000.0, + (groundPt.y - sunEcefVec.y) / 1000.0, + (groundPt.z - sunEcefVec.z) / 1000.0}; + return target()->shape()->incidenceAngle(sunVec); + } + + + /** + * Compute the slant distance form the sensor to the ground point at the + * currently set time. + * + * @returns @b double The distance from the sensor to the ground point in kilometers + */ + double CSMCamera::SlantDistance() const { + std::vector sensorPosition = sensorPositionBodyFixed(); + SurfacePoint groundPoint = GetSurfacePoint(); + + std::vector sensorToGround = { + groundPoint.GetX().kilometers() - (sensorPosition[0]), + groundPoint.GetY().kilometers() - (sensorPosition[1]), + groundPoint.GetZ().kilometers() - (sensorPosition[2])}; + + return sqrt( + sensorToGround[0] * sensorToGround[0] + + sensorToGround[1] * sensorToGround[1] + + sensorToGround[2] * sensorToGround[2]); + } + + + /** + * Calculates and returns the distance from the spacecraft to the target center at the + * currently set time. + * + * @returns @b double Distance to the center of the target from the spacecraft in kilometers. + */ + double CSMCamera::targetCenterDistance() const { + std::vector sensorPosition = sensorPositionBodyFixed(); + return sqrt( + sensorPosition[0] * sensorPosition[0] + + sensorPosition[1] * sensorPosition[1] + + sensorPosition[2] * sensorPosition[2]); + } + + + /** + * Set the time and update the sensor position and orientation. + * + * This is not supported for CSM cameras because the time is a function of the + * image coordinate and the two cannot be changed independently. + * + * @param time The time to set + */ + void CSMCamera::setTime(const iTime &time) { + QString msg = "Setting the image time is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Returns the sub-solar latitude/longitude in universal coordinates (0-360 + * positive east, ocentric). + * + * This is not supported for CSM sensors because we cannot get the position + * of the sun, only the illumination direction. + * + * @param lat Sub-solar latitude + * @param lon Sub-solar longitude + */ + void CSMCamera::subSolarPoint(double &lat, double &lon) { + QString msg = "Sub solar point is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Returns the pixel ifov offsets from center of pixel. The first vertex is the top left. + * + * The CSM API does not support this type of internal information about the sensor. + * + * @returns @b QList The field of view offsets + */ + QList CSMCamera::PixelIfovOffsets() { + QString msg = "Pixel Field of View is not supported for CSM camera models"; + throw IException(IException::User, msg, _FILEINFO_); + } + + + /** + * Get the body fixed position of the sun in kilometers. + * + * This is not supported for CSM sensors because we cannot get the position + * of the sun, only the illumination direction. + * + * @param[out] p The position of the sun + */ + void CSMCamera::sunPosition(double p[3]) const { + QString msg = "Sun position is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Get the SpicePosition object that contains the state information for the sun in J2000. + * + * This is not supported for CSM sensors because we cannot get the position + * of the sun, only the illumination direction. + * + * @returns @b SpicePosition* A pointer to the SpicePosition object for the Sun + */ + SpicePosition *CSMCamera::sunPosition() const { + QString msg = "Sun position is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Get the SpicePosition object the contains the state information for the sensor in J2000. + * + * This is not supported for CSM sensors because we can only query the sensor position + * and velocity at specific image coordinates or times. We cannot access the internal + * representation inside of the CSM model, if it even exists. + * + * @returns @b SpicePosition* A pointer to the SpicePosition object for the sensor + */ + SpicePosition *CSMCamera::instrumentPosition() const { + QString msg = "Instrument position is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Get the SpiceRotation object the contains the orientation of the target body + * relative to J2000. + * + * This is not supported for CSM sensors because the CSM API only supports the + * body fixed coordinate system and does not provide rotations to any others. + * + * @returns @b SpiceRotation* A pointer to the SpiceRotation object for the body orientation + */ + SpiceRotation *CSMCamera::bodyRotation() const { + QString msg = "Target body orientation is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Get the SpiceRotation object the contains the orientation of the sensor + * relative to J2000. + * + * This is not supported for CSM sensors because the CSM API only supports the + * body fixed coordinate system and does not provide rotations to any others. + * + * @returns @b SpiceRotation* A pointer to the SpiceRotation object for the sensor orientation + */ + SpiceRotation *CSMCamera::instrumentRotation() const { + QString msg = "Instrument orientation is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Computes the solar longitude for the given ephemeris time. If the target + * is sky, the longitude is set to -999.0. + * + * This is not supported for CSM models because we cannot get the sun position. + * + * @param et Ephemeris time + */ + void CSMCamera::computeSolarLongitude(iTime et) { + QString msg = "Solar longitude is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Computes the distance to the sun from the currently set ground point + * + * This is not supported for CSM models because we cannot get the sun position. + * + * @returns @b double The distance to the sun + */ + double CSMCamera::SolarDistance() const { + QString msg = "Solar distance is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Computes the Right Ascension of the currently set image coordinate. + * + * This is not supported for CSM sensors because the CSM API only supports the + * body fixed coordinate system and does not provide rotations to any others. + * + * @returns @b double The Right Ascension + */ + double CSMCamera::RightAscension() { + QString msg = "Right Ascension is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + + /** + * Computes the Declination of the currently set image coordinate. + * + * This is not supported for CSM sensors because the CSM API only supports the + * body fixed coordinate system and does not provide rotations to any others. + * + * @returns @b double The Declination + */ + double CSMCamera::Declination() { + QString msg = "Declination is not supported for CSM camera models"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } +} diff --git a/isis/src/base/objs/CSMCamera/CSMCamera.h b/isis/src/base/objs/CSMCamera/CSMCamera.h new file mode 100644 index 0000000000..65dbde302f --- /dev/null +++ b/isis/src/base/objs/CSMCamera/CSMCamera.h @@ -0,0 +1,144 @@ +#ifndef CSMCamera_h +#define CSMCamera_h +/** This is free and unencumbered software released into the public domain. + +The authors of ISIS do not claim copyright on the contents of this file. +For more details about the LICENSE terms and the AUTHORS, you will +find files of those names at the top level of this repository. **/ + +/* SPDX-License-Identifier: CC0-1.0 */ + +#include "Camera.h" +#include "iTime.h" +#include "Target.h" + +#include + +#include +#include + +#include "csm/csm.h" +#include "csm/RasterGM.h" + +namespace Isis { + class StringBlob; + class CSMCamera : public Camera { + + public: + // constructors + CSMCamera(Cube &cube); +// CSMCamera(Cube &cube, QString pluginName, QString modelName, QString stateString); + + //! Destroys the CSMCamera object. + ~CSMCamera() {}; + + /** + * The CSM camera needs a bogus type for now. + * + * @return CameraType Camera::Point + */ + virtual CameraType GetCameraType() const { + return Point; + } + + /** + * CK frame ID - - Instrument Code from spacit run on CK + * + * @return @b int The appropriate instrument code for the "Camera-matrix" + * Kernel Frame ID + */ + virtual int CkFrameId() const { return (-1); } + + /** + * CK Reference ID - J2000 + * + * @return @b int The appropriate instrument code for the "Camera-matrix" + * Kernel Reference ID + */ + virtual int CkReferenceId() const { return (-1); } + + /** + * SPK Center ID - 6 (Saturn) + * + * @return @b int The appropriate instrument code for the Spacecraft + * Kernel Center ID + */ + virtual int SpkCenterId() const { return -1; } + + /** + * SPK Reference ID - J2000 + * + * @return @b int The appropriate instrument code for the Spacecraft + * Kernel Reference ID + */ + virtual int SpkReferenceId() const { return (-1); } + + virtual QList PixelIfovOffsets(); + + virtual bool SetImage(const double sample, const double line); + + virtual bool SetGround(Latitude latitude, Longitude longitude); + virtual bool SetGround(const SurfacePoint &surfacePt); + virtual bool SetUniversalGround(const double latitude, const double longitude); + virtual bool SetUniversalGround(const double latitude, const double longitude, double radius); + + virtual void setTime(const iTime &time); + + virtual double LineResolution(); + virtual double SampleResolution(); + virtual double DetectorResolution(); + virtual double ObliqueLineResolution(); + virtual double ObliqueSampleResolution(); + virtual double ObliqueDetectorResolution(); + + virtual double parentLine() const; + virtual double parentSample() const; + + virtual void subSpacecraftPoint(double &lat, double &lon); + virtual void subSpacecraftPoint(double &lat, double &lon, double line, double sample); + virtual void subSolarPoint(double &lat, double &lon); + + virtual double PhaseAngle() const; + virtual double EmissionAngle() const; + virtual double IncidenceAngle() const; + + virtual SpicePosition *sunPosition() const; + virtual SpicePosition *instrumentPosition() const; + virtual SpiceRotation *bodyRotation() const; + virtual SpiceRotation *instrumentRotation() const; + + virtual void instrumentBodyFixedPosition(double p[3]) const; + virtual void sunPosition(double p[3]) const; + virtual double SolarDistance() const; + + virtual double SlantDistance() const; + virtual double targetCenterDistance() const; + + virtual double RightAscension(); + virtual double Declination(); + + protected: + void setTarget(Pvl label); + + std::vector sensorPositionBodyFixed() const; + std::vector sensorPositionBodyFixed(double line, double sample) const; + + virtual void computeSolarLongitude(iTime et); + + private: + void init(Cube &cube, QString pluginName, QString modelName, QString stateString); + + csm::RasterGM *m_model; //! CSM sensor model + iTime m_refTime; //! The reference time that all model image times are relative to + + void isisToCsmPixel(double line, double sample, csm::ImageCoord &csmPixel) const; + void csmToIsisPixel(csm::ImageCoord csmPixel, double &line, double &sample) const; + csm::EcefCoord isisToCsmGround(const SurfacePoint &groundPt) const; + SurfacePoint csmToIsisGround(const csm::EcefCoord &groundPt) const; + + virtual std::vector ImagePartials(SurfacePoint groundPoint); + virtual std::vector ImagePartials(); + + }; +}; +#endif diff --git a/isis/src/base/objs/CSMCamera/Makefile b/isis/src/base/objs/CSMCamera/Makefile new file mode 100644 index 0000000000..f122bc8822 --- /dev/null +++ b/isis/src/base/objs/CSMCamera/Makefile @@ -0,0 +1,7 @@ +ifeq ($(ISISROOT), $(BLANK)) +.SILENT: +error: + echo "Please set ISISROOT"; +else + include $(ISISROOT)/make/isismake.objs +endif \ No newline at end of file diff --git a/isis/src/base/objs/Camera/Camera.cpp b/isis/src/base/objs/Camera/Camera.cpp index b33d3011c6..5d2b95433f 100644 --- a/isis/src/base/objs/Camera/Camera.cpp +++ b/isis/src/base/objs/Camera/Camera.cpp @@ -52,10 +52,10 @@ namespace Isis { * @param cube The Pvl label from the cube is used to create the Camera object. */ Camera::Camera(Cube &cube) : Sensor(cube) { - - m_instrumentId = cube.label()->findGroup("Instrument", + + m_instrumentId = cube.label()->findGroup("Instrument", PvlObject::FindOptions::Traverse).findKeyword("InstrumentId")[0]; - + m_instrumentNameLong = "Unknown"; m_instrumentNameShort = "Unknown"; m_spacecraftNameLong = "Unknown"; @@ -192,12 +192,12 @@ namespace Isis { // The projection is a sky map else if (p_projection->IsSky()) { - return SetImageSkyMapProjection(sample, line, shape); + return SetImageSkyMapProjection(sample, line, shape); } // We have map projected camera model else { - return SetImageMapProjection(sample, line, shape); + return SetImageMapProjection(sample, line, shape); } // failure @@ -209,19 +209,19 @@ namespace Isis { /** * @brief Sets the sample/line values of the image to get the lat/lon values with * a time offset of deltaT. - * - * Warning: The deltaT parameter was added specifically for pixel2map to use for - * the Dawn VIR camera. It is used to adjust the pointing to its location at specific - * times like the times at the beginning, middle, and end of exposure for a specific pixel, - * when the correct deltaT can be determined to achieve these results. - * - * Do not use this verstion of SetImage with a deltaT unless you understand exactly what this - * does. - * + * + * Warning: The deltaT parameter was added specifically for pixel2map to use for + * the Dawn VIR camera. It is used to adjust the pointing to its location at specific + * times like the times at the beginning, middle, and end of exposure for a specific pixel, + * when the correct deltaT can be determined to achieve these results. + * + * Do not use this verstion of SetImage with a deltaT unless you understand exactly what this + * does. + * * @param sample Sample coordinate of the cube. - * @param line Line coordinate of the cube. - * @param deltaT seconds from the center exposure time - * + * @param line Line coordinate of the cube. + * @param deltaT seconds from the center exposure time + * * @return @b bool Returns True if the image was set successfully and False if it * was not. */ @@ -266,12 +266,12 @@ namespace Isis { // The projection is a sky map else if (p_projection->IsSky()) { - return SetImageSkyMapProjection(sample, line, shape); + return SetImageSkyMapProjection(sample, line, shape); } - + // We have map projected camera model else { - return SetImageMapProjection(sample, line, shape); + return SetImageMapProjection(sample, line, shape); } // failure @@ -281,13 +281,13 @@ namespace Isis { /** - * @brief Sets the sample/line values of the image to get the lat/lon values for a Map Projected - * image. - * + * @brief Sets the sample/line values of the image to get the lat/lon values for a Map Projected + * image. + * * @param sample Sample coordinate of the cube * @param line Line coordinate of the cube * @param shape shape of the target - * + * * @return bool Returns True if the image was set successfully and False if it * was not. */ @@ -337,18 +337,18 @@ namespace Isis { } } shape->clearSurfacePoint(); - return false; + return false; } /** - * @brief Sets the sample/line values of the image to get the lat/lon values for a Skymap Projected - * image. - * + * @brief Sets the sample/line values of the image to get the lat/lon values for a Skymap Projected + * image. + * * @param sample Sample coordinate of the cube * @param line Line coordinate of the cube * @param shape shape of the target - * + * * @return bool Returns True if the image was set successfully and False if it * was not. */ @@ -364,7 +364,7 @@ namespace Isis { } } shape->clearSurfacePoint(); - return false; + return false; } @@ -584,10 +584,10 @@ namespace Isis { if(HasSurfaceIntersection()){ - double thetaRad; + double thetaRad; thetaRad = EmissionAngle()*DEG2RAD; - if (thetaRad < HALFPI) { + if (thetaRad < HALFPI) { return DetectorResolution()/cos(thetaRad); } @@ -627,7 +627,6 @@ namespace Isis { * @return @b double The sample resolution */ double Camera::SampleResolution() { - return DetectorResolution() * p_detectorMap->SampleScaleFactor(); } @@ -638,7 +637,6 @@ namespace Isis { * @return @b double The sample resolution */ double Camera::ObliqueSampleResolution() { - return ObliqueDetectorResolution() * p_detectorMap->SampleScaleFactor(); } @@ -661,7 +659,6 @@ namespace Isis { * @return @b double The line resolution */ double Camera::ObliqueLineResolution() { - return ObliqueDetectorResolution() * p_detectorMap->LineScaleFactor(); } @@ -2460,7 +2457,7 @@ namespace Isis { * close time) is the maximum value of those ephemeris times. This method must * be called before a call to the Spice::createCache() method. It is called * in the LoadCache() method. - * + * * @returns pair A pair containing the start and end ephemeris times * * @throw iException::Programmer - "Unable to find time range for the @@ -2506,10 +2503,10 @@ namespace Isis { * of lines in the beta cube and adds 1, since we need at least 2 points for * interpolation. This method must be called before a call to the * Spice::createCache() method. It is called in the LoadCache() method. - * + * * @param startTime Starting ephemeris time to cache * @param endTime Ending ephemeris time to cache - * + * * @returns int The calculated spice cache size * * @throw iException::Programmer - "A cache has already been created." @@ -2683,7 +2680,7 @@ namespace Isis { * * @return @b double Sample Number */ - double Camera::Sample() { + double Camera::Sample() const { return p_childSample; } @@ -2693,7 +2690,7 @@ namespace Isis { * * @return @b int Band */ - int Camera::Band() { + int Camera::Band() const { return p_childBand; } @@ -2703,7 +2700,7 @@ namespace Isis { * * @return @b double Line Number */ - double Camera::Line() { + double Camera::Line() const { return p_childLine; } @@ -2744,7 +2741,7 @@ namespace Isis { * Returns the pixel ifov offsets from center of pixel, which defaults to the * (pixel pitch * summing mode ) / 2. If an instrument has a non-square ifov, it must implement * this method to return the offsets from the center of the pixel. - * + * * @returns QList A list of offsets * */ @@ -2862,8 +2859,8 @@ namespace Isis { CameraSkyMap *Camera::SkyMap() { return p_skyMap; } - - + + /** * This method returns the InstrumentId as it appears in the cube. * @@ -3053,7 +3050,7 @@ namespace Isis { /** * Return the exposure duration for the pixel that the camera is set to. - * + * * @return @b double The exposure duration in seconds for the pixel that the camera is set to. */ double Camera::exposureDuration() const { @@ -3063,11 +3060,11 @@ namespace Isis { /** * Return the exposure duration for the pixel at the given line, sample and band. - * + * * @param sample The sample of the desired pixel. * @param line The line of the desired pixel. * @param band The band of the desired pixel. Defaults to 1. - * + * * @return @b double The exposure duration for the desired pixel in seconds. */ double Camera::exposureDuration(const double sample, const double line, const int band) const { diff --git a/isis/src/base/objs/Camera/Camera.h b/isis/src/base/objs/Camera/Camera.h index 6813033249..174b13fd34 100644 --- a/isis/src/base/objs/Camera/Camera.h +++ b/isis/src/base/objs/Camera/Camera.h @@ -223,10 +223,10 @@ namespace Isis { * an error in the original formula, and updated the documention for this * function. Fixes #4614. * @history 2017-08-30 Summer Stapleton - Updated documentation. References #4807. - * @history 2017-01-11 Christopher Combs - Added bool deleteExisting to SetDistortionMap to + * @history 2017-01-11 Christopher Combs - Added bool deleteExisting to SetDistortionMap to * prevent a segfault when the distortion map is incomplete. Fixes $5163. - * @history 2018-07-12 Summer Stapleton - Added m_instrumentId and instrumentId() in order to - * collect the InstrumentId from the original cube label for + * @history 2018-07-12 Summer Stapleton - Added m_instrumentId and instrumentId() in order to + * collect the InstrumentId from the original cube label for * comparisons related to image imports in ipce. References #5460. */ @@ -246,8 +246,8 @@ namespace Isis { virtual bool SetUniversalGround(const double latitude, const double longitude); virtual bool SetUniversalGround(const double latitude, const double longitude, const double radius); - bool SetGround(Latitude latitude, Longitude longitude); - bool SetGround(const SurfacePoint & surfacePt); + virtual bool SetGround(Latitude latitude, Longitude longitude); + virtual bool SetGround(const SurfacePoint & surfacePt); bool SetRightAscensionDeclination(const double ra, const double dec); void LocalPhotometricAngles(Angle & phase, Angle & incidence, @@ -260,9 +260,9 @@ namespace Isis { int ReferenceBand() const; bool HasReferenceBand() const; virtual void SetBand(const int band); - virtual double Sample(); - int Band(); - virtual double Line(); + virtual double Sample() const; + int Band() const; + virtual double Line() const; bool GroundRange(double &minlat, double &maxlat, double &minlon, double &maxlon, Pvl &pvl); @@ -270,15 +270,15 @@ namespace Isis { double &minRingLongitude, double &maxRingLongitude, Pvl &pvl); bool IntersectsLongitudeDomain(Pvl &pvl); - double PixelResolution(); - double LineResolution(); - double SampleResolution(); - double DetectorResolution(); + virtual double PixelResolution(); + virtual double LineResolution(); + virtual double SampleResolution(); + virtual double DetectorResolution(); - double ObliqueDetectorResolution(); - double ObliqueSampleResolution(); - double ObliqueLineResolution(); - double ObliquePixelResolution(); + virtual double ObliqueDetectorResolution(); + virtual double ObliqueSampleResolution(); + virtual double ObliqueLineResolution(); + virtual double ObliquePixelResolution(); virtual double resolution(); @@ -315,7 +315,7 @@ namespace Isis { CameraDetectorMap *DetectorMap(); CameraGroundMap *GroundMap(); CameraSkyMap *SkyMap(); - + QString instrumentId(); QString instrumentNameLong() const; @@ -486,7 +486,7 @@ namespace Isis { // slant range changes. friend class RadarGroundMap; //!< A friend class to calculate focal length friend class RadarSlantRangeMap; //!< A friend class to calculate focal length - + QString m_instrumentId; //!< The InstrumentId as it appears on the cube. QString m_instrumentNameLong; //!< Full instrument name @@ -494,16 +494,21 @@ namespace Isis { QString m_spacecraftNameLong; //!< Full spacecraft name QString m_spacecraftNameShort; //!< Shortened spacecraft name + double p_childSample; //!< Sample value for child + double p_childLine; //!< Line value for child + AlphaCube *p_alphaCube; //!< A pointer to the AlphaCube + + bool p_pointComputed; //!< Flag showing if Sample/Line has been computed private: void GroundRangeResolution(); void ringRangeResolution(); double ComputeAzimuth(const double lat, const double lon); bool RawFocalPlanetoImage(); - // SetImage helper functions: - // bool SetImageNoProjection(const double sample, const double line); + // SetImage helper functions: + // bool SetImageNoProjection(const double sample, const double line); bool SetImageMapProjection(const double sample, const double line, ShapeModel *shape); - bool SetImageSkyMapProjection(const double sample, const double line, ShapeModel *shape); + bool SetImageSkyMapProjection(const double sample, const double line, ShapeModel *shape); double p_focalLength; //!< The focal length, in units of millimeters @@ -522,7 +527,6 @@ namespace Isis { /** Flag showing if ground range was computed successfully.*/ bool p_groundRangeComputed; - bool p_pointComputed; //!< Flag showing if Sample/Line has been computed int p_samples; //!< The number of samples in the image int p_lines; //!< The number of lines in the image @@ -551,9 +555,7 @@ namespace Isis { /** Flag showing if ring range was computed successfully.*/ bool p_ringRangeComputed; - AlphaCube *p_alphaCube; //!< A pointer to the AlphaCube - double p_childSample; //!< Sample value for child - double p_childLine; //!< Line value for child + int p_childBand; //!< Band value for child CameraDistortionMap *p_distortionMap; //!< A pointer to the DistortionMap CameraFocalPlaneMap *p_focalPlaneMap; //!< A pointer to the FocalPlaneMap diff --git a/isis/src/base/objs/CameraFactory/CameraFactory.cpp b/isis/src/base/objs/CameraFactory/CameraFactory.cpp index 47f1f7dd92..605d890e0c 100644 --- a/isis/src/base/objs/CameraFactory/CameraFactory.cpp +++ b/isis/src/base/objs/CameraFactory/CameraFactory.cpp @@ -14,16 +14,22 @@ find files of those names at the top level of this repository. **/ #include "CameraFactory.h" #include "Camera.h" +#include "CSMCamera.h" #include "FileName.h" #include "IException.h" #include "Plugin.h" #include "Preference.h" +#include "csm/csm.h" +#include "csm/Model.h" +#include "csm/Plugin.h" + using namespace csm; using namespace std; namespace Isis { Plugin CameraFactory::m_cameraPlugin; + bool CameraFactory::m_initialized = false; /** * Creates a Camera object using Pvl Specifications @@ -39,56 +45,70 @@ namespace Isis { Camera *CameraFactory::Create(Cube &cube) { // Try to load a plugin file in the current working directory and then // load the system file + initPlugin(); try { - // First get the spacecraft and instrument and combine them - Pvl &lab = *cube.label(); - PvlGroup &inst = lab.findGroup("Instrument", Isis::Pvl::Traverse); - QString spacecraft = (QString) inst["SpacecraftName"]; - QString name = (QString) inst["InstrumentId"]; - spacecraft = spacecraft.toUpper(); - name = name.toUpper(); - QString group = spacecraft + "/" + name; - group = group.remove(" "); - - PvlGroup &kerns = lab.findGroup("Kernels", Isis::Pvl::Traverse); - // Default version 1 for backwards compatibility (spiceinit'd cubes before camera model versioning) - if (!kerns.hasKeyword("CameraVersion")) { - kerns.addKeyword(PvlKeyword("CameraVersion", "1")); + // Is there a CSM blob on the cube? + if (cube.hasBlob("String", "CSMState")) { + // Create ISIS CSM Camera Model + try { + return new CSMCamera(cube); + } + catch (IException &e) { + QString msg = "Unable to create CSM camera using CSMState Cube blob."; + throw IException(e, IException::Unknown, msg, _FILEINFO_); + } } + else { + // First get the spacecraft and instrument and combine them + Pvl &lab = *cube.label(); + PvlGroup &inst = lab.findGroup("Instrument", Isis::Pvl::Traverse); + QString spacecraft = (QString) inst["SpacecraftName"]; + QString name = (QString) inst["InstrumentId"]; + spacecraft = spacecraft.toUpper(); + name = name.toUpper(); + QString group = spacecraft + "/" + name; + group = group.remove(" "); + + PvlGroup &kerns = lab.findGroup("Kernels", Isis::Pvl::Traverse); + // Default version 1 for backwards compatibility (spiceinit'd cubes before camera model versioning) + if (!kerns.hasKeyword("CameraVersion")) { + kerns.addKeyword(PvlKeyword("CameraVersion", "1")); + } - int cameraOriginalVersion = (int)kerns["CameraVersion"]; - int cameraNewestVersion = CameraVersion(cube); + int cameraOriginalVersion = (int)kerns["CameraVersion"]; + int cameraNewestVersion = CameraVersion(cube); - if (cameraOriginalVersion != cameraNewestVersion) { - string msg = "The camera model used to create a camera for this cube is out of date, " \ - "please re-run spiceinit on the file or process with an old Isis version " \ - "that has the correct camera model."; - throw IException(IException::Unknown, msg, _FILEINFO_); - } + if (cameraOriginalVersion != cameraNewestVersion) { + string msg = "The camera model used to create a camera for this cube is out of date, " \ + "please re-run spiceinit on the file or process with an old Isis version " \ + "that has the correct camera model."; + throw IException(IException::Unknown, msg, _FILEINFO_); + } - // See if we have a camera model plugin - QFunctionPointer ptr; - try { - ptr = m_cameraPlugin.GetPlugin(group); - } - catch(IException &e) { - QString msg = "Unsupported camera model, unable to find plugin for "; - msg += "SpacecraftName [" + spacecraft + "] with InstrumentId ["; - msg += name + "]"; - throw IException(e, IException::Unknown, msg, _FILEINFO_); - } + // See if we have a camera model plugin + QFunctionPointer ptr; + try { + ptr = m_cameraPlugin.GetPlugin(group); + } + catch(IException &e) { + QString msg = "Unsupported camera model, unable to find plugin for "; + msg += "SpacecraftName [" + spacecraft + "] with InstrumentId ["; + msg += name + "]"; + throw IException(e, IException::Unknown, msg, _FILEINFO_); + } - // Now cast that pointer in the proper way - Camera * (*plugin)(Isis::Cube &cube); - plugin = (Camera * ( *)(Isis::Cube &cube)) ptr; + // Now cast that pointer in the proper way + Camera * (*plugin)(Isis::Cube &cube); + plugin = (Camera * ( *)(Isis::Cube &cube)) ptr; - // Create the projection as requested - return (*plugin)(cube); + // Create the camera as requested + return (*plugin)(cube); + } } catch(IException &e) { - string message = "Unable to initialize camera model from group [Instrument]"; + string message = "Unable to initialize camera model in Camera Factory."; throw IException(e, IException::Unknown, message, _FILEINFO_); } } @@ -99,33 +119,35 @@ namespace Isis { * directories specified in IsisPreferences for CSM cameras. */ void CameraFactory::initPlugin() { + if (!m_initialized) { + // Handle the ISIS camera plugins + if (m_cameraPlugin.fileName() == "") { + FileName localFile("Camera.plugin"); + if (localFile.fileExists()) + m_cameraPlugin.read(localFile.expanded()); + + FileName systemFile("$ISISROOT/lib/Camera.plugin"); + if (systemFile.fileExists()) + m_cameraPlugin.read(systemFile.expanded()); + } - // Handle the ISIS camera plugins - if (m_cameraPlugin.fileName() == "") { - FileName localFile("Camera.plugin"); - if (localFile.fileExists()) - m_cameraPlugin.read(localFile.expanded()); - - FileName systemFile("$ISISROOT/lib/Camera.plugin"); - if (systemFile.fileExists()) - m_cameraPlugin.read(systemFile.expanded()); - } - - // Find the CSM plugins by searching the directories identified in the Preferences. - // Load the found libraries. This causes the static instance(s) to be constructed, - // and thus registering the model with the csm Plugin class. - Preference &p = Preference::Preferences(); - PvlGroup &grp = p.findGroup("Plugins", Isis::Pvl::Traverse); - for (int i = 0; iHasSurfaceIntersection()) { @@ -389,17 +387,27 @@ namespace Isis { double pB[3], spB[3], sB[3]; QString utc; - double ssplat, ssplon, sslat, sslon, ocentricLat, ographicLat, pe360Lon, pw360Lon; + double ssplat, ssplon, ocentricLat, ographicLat, pe360Lon, pw360Lon; { gp->findKeyword("FileName").setValue(m_currentCube->fileName()); gp->findKeyword("Sample").setValue(toString(m_camera->Sample())); gp->findKeyword("Line").setValue(toString(m_camera->Line())); gp->findKeyword("PixelValue").setValue(PixelToString(b[0])); - gp->findKeyword("RightAscension").setValue(toString( - m_camera->RightAscension()), "DEGREE"); - gp->findKeyword("Declination").setValue(toString( - m_camera->Declination()), "DEGREE"); + try { + gp->findKeyword("RightAscension").setValue(toString( + m_camera->RightAscension()), "DEGREE"); + } + catch (IException &e) { + gp->findKeyword("RightAscension").setValue("Null"); + } + try { + gp->findKeyword("Declination").setValue(toString( + m_camera->Declination()), "DEGREE"); + } + catch (IException &e) { + gp->findKeyword("Declination").setValue("Null"); + } ocentricLat = m_camera->UniversalLatitude(); gp->findKeyword("PlanetocentricLatitude").setValue(toString(ocentricLat), "DEGREE"); @@ -480,30 +488,61 @@ namespace Isis { gp->findKeyword("SubSpacecraftGroundAzimuth").setValue( toString(subspcgrdaz), "DEGREE"); - m_camera->sunPosition(sB); - gp->findKeyword("SunPosition").addValue(toString(sB[0]), "km"); - gp->findKeyword("SunPosition").addValue(toString(sB[1]), "km"); - gp->findKeyword("SunPosition").addValue(toString(sB[2]), "km"); - gp->findKeyword("SunPosition").addComment("Sun Information"); + try { + m_camera->sunPosition(sB); + gp->findKeyword("SunPosition").addValue(toString(sB[0]), "km"); + gp->findKeyword("SunPosition").addValue(toString(sB[1]), "km"); + gp->findKeyword("SunPosition").addValue(toString(sB[2]), "km"); + gp->findKeyword("SunPosition").addComment("Sun Information"); + } + catch (IException &e) { + gp->findKeyword("SunPosition").addValue("Null"); + gp->findKeyword("SunPosition").addValue("Null"); + gp->findKeyword("SunPosition").addValue("Null"); + gp->findKeyword("SunPosition").addComment("Sun Information"); + } - double sunAzi = m_camera->SunAzimuth(); - if (Isis::IsValidPixel(sunAzi)) { - gp->findKeyword("SubSolarAzimuth").setValue(toString(sunAzi), "DEGREE"); + try { + double sunAzi = m_camera->SunAzimuth(); + if (Isis::IsValidPixel(sunAzi)) { + gp->findKeyword("SubSolarAzimuth").setValue(toString(sunAzi), "DEGREE"); + } + else { + gp->findKeyword("SubSolarAzimuth").setValue("NULL"); + } } - else { + catch(IException &e) { gp->findKeyword("SubSolarAzimuth").setValue("NULL"); } - gp->findKeyword("SolarDistance").setValue(toString( - m_camera->SolarDistance()), "AU"); - m_camera->subSolarPoint(sslat, sslon); - gp->findKeyword("SubSolarLatitude").setValue(toString(sslat), "DEGREE"); - gp->findKeyword("SubSolarLongitude").setValue(toString(sslon), "DEGREE"); - double subsolgrdaz = m_camera->GroundAzimuth(m_camera->UniversalLatitude(), - m_camera->UniversalLongitude(), - sslat, sslon); - gp->findKeyword("SubSolarGroundAzimuth").setValue( - toString(subsolgrdaz), "DEGREE"); + try { + gp->findKeyword("SolarDistance").setValue(toString( + m_camera->SolarDistance()), "AU"); + } + catch(IException &e) { + gp->findKeyword("SolarDistance").setValue("NULL"); + } + try { + double sslat, sslon; + m_camera->subSolarPoint(sslat, sslon); + gp->findKeyword("SubSolarLatitude").setValue(toString(sslat), "DEGREE"); + gp->findKeyword("SubSolarLongitude").setValue(toString(sslon), "DEGREE"); + + try { + double subsolgrdaz = m_camera->GroundAzimuth(m_camera->UniversalLatitude(), + m_camera->UniversalLongitude(), + sslat, sslon); + gp->findKeyword("SubSolarGroundAzimuth").setValue(toString(subsolgrdaz), "DEGREE"); + } + catch(IException &e) { + gp->findKeyword("SubSolarGroundAzimuth").setValue("NULL"); + } + } + catch(IException &e) { + gp->findKeyword("SubSolarLatitude").setValue("NULL"); + gp->findKeyword("SubSolarLongitude").setValue("NULL"); + gp->findKeyword("SubSolarGroundAzimuth").setValue("NULL"); + } gp->findKeyword("Phase").setValue(toString(m_camera->PhaseAngle()), "DEGREE"); gp->findKeyword("Phase").addComment("Illumination and Other"); @@ -525,10 +564,20 @@ namespace Isis { gp->findKeyword("EphemerisTime").addComment("Time"); utc = m_camera->time().UTC(); gp->findKeyword("UTC").setValue(utc); - gp->findKeyword("LocalSolarTime").setValue(toString( - m_camera->LocalSolarTime()), "hour"); - gp->findKeyword("SolarLongitude").setValue(toString( - m_camera->solarLongitude().degrees()), "DEGREE"); + try { + gp->findKeyword("LocalSolarTime").setValue(toString( + m_camera->LocalSolarTime()), "hour"); + } + catch (IException &e) { + gp->findKeyword("LocalSolarTime").setValue("Null"); + } + try { + gp->findKeyword("SolarLongitude").setValue(toString( + m_camera->solarLongitude().degrees()), "DEGREE"); + } + catch (IException &e) { + gp->findKeyword("SolarLongitude").setValue("Null"); + } std::vectorlookB = m_camera->lookDirectionBodyFixed(); gp->findKeyword("LookDirectionBodyFixed").addValue(toString(lookB[0]), "DEGREE"); @@ -536,17 +585,30 @@ namespace Isis { gp->findKeyword("LookDirectionBodyFixed").addValue(toString(lookB[2]), "DEGREE"); gp->findKeyword("LookDirectionBodyFixed").addComment("Look Direction Unit Vectors in Body Fixed, J2000, and Camera Coordinate Systems."); - std::vectorlookJ = m_camera->lookDirectionJ2000(); - gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[0]), "DEGREE"); - gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[1]), "DEGREE"); - gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[2]), "DEGREE"); - - double lookC[3]; - m_camera->LookDirection(lookC); - gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[0]), "DEGREE"); - gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[1]), "DEGREE"); - gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[2]), "DEGREE"); + try { + std::vectorlookJ = m_camera->lookDirectionJ2000(); + gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[0]), "DEGREE"); + gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[1]), "DEGREE"); + gp->findKeyword("LookDirectionJ2000").addValue(toString(lookJ[2]), "DEGREE"); + } + catch (IException &e) { + gp->findKeyword("LookDirectionJ2000").addValue("Null"); + gp->findKeyword("LookDirectionJ2000").addValue("Null"); + gp->findKeyword("LookDirectionJ2000").addValue("Null"); + } + try { + double lookC[3]; + m_camera->LookDirection(lookC); + gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[0]), "DEGREE"); + gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[1]), "DEGREE"); + gp->findKeyword("LookDirectionCamera").addValue(toString(lookC[2]), "DEGREE"); + } + catch (IException &e) { + gp->findKeyword("LookDirectionCamera").addValue("Null"); + gp->findKeyword("LookDirectionCamera").addValue("Null"); + gp->findKeyword("LookDirectionCamera").addValue("Null"); + } if (allowErrors) gp->findKeyword("Error").setValue("NULL"); diff --git a/isis/src/base/objs/Chip/Chip.truth b/isis/src/base/objs/Chip/Chip.truth index 24f9e6c0c4..1934e8dcca 100644 --- a/isis/src/base/objs/Chip/Chip.truth +++ b/isis/src/base/objs/Chip/Chip.truth @@ -179,14 +179,14 @@ Try to load a cube that is not camera or map projection: **USER ERROR** Can not geom chip. Chip cube [] is not a camera or map projection. **ERROR** Unable to initialize cube projection from file []. **ERROR** Unable to find PVL group [Mapping] in file []. -**ERROR** Unable to initialize camera model from group [Instrument]. +**ERROR** Unable to initialize camera model in Camera Factory. **ERROR** Unable to find PVL group [Instrument] in file []. Try to load a cube with a match cube that is not camera or map projection: **USER ERROR** Can not geom chip. Match chip cube [] is not a camera or map projection. **ERROR** Unable to initialize cube projection from file []. **ERROR** Unable to find PVL group [Mapping] in file []. -**ERROR** Unable to initialize camera model from group [Instrument]. +**ERROR** Unable to initialize camera model in Camera Factory. **ERROR** Unable to find PVL group [Instrument] in file []. Try to load a cube with match chip and cube that can not find at least 3 points for Affine Transformation: diff --git a/isis/src/base/objs/Cube/Cube.cpp b/isis/src/base/objs/Cube/Cube.cpp index b641bb7d9d..5956137bbc 100644 --- a/isis/src/base/objs/Cube/Cube.cpp +++ b/isis/src/base/objs/Cube/Cube.cpp @@ -1801,16 +1801,17 @@ namespace Isis { /** - * Check to see if the cube contains a pvl table by the provided name + * Check to see if the cube contains a BLOB. * - * @param name The name of the pvl table to search for + * @param type The type of the BLOB to search for + * @param name The name of the BLOB to search for * - * @return bool True if the pvl table was found + * @return bool True if the BLOB was found */ - bool Cube::hasTable(const QString &name) { + bool Cube::hasBlob(const QString &type, const QString &name) { for(int o = 0; o < label()->objects(); o++) { PvlObject &obj = label()->object(o); - if (obj.isNamed("Table")) { + if (obj.isNamed(type)) { if (obj.hasKeyword("Name")) { QString temp = (QString) obj["Name"]; temp = temp.toUpper(); @@ -1824,6 +1825,18 @@ namespace Isis { } + /** + * Check to see if the cube contains a pvl table by the provided name + * + * @param name The name of the pvl table to search for + * + * @return bool True if the pvl table was found + */ + bool Cube::hasTable(const QString &name) { + return hasBlob("Table", name); + } + + /** * Adds a group in a Label to the cube. If the group already * exists in the cube it will be completely overwritten. diff --git a/isis/src/base/objs/Cube/Cube.h b/isis/src/base/objs/Cube/Cube.h index 4d00d1e46f..010136e235 100644 --- a/isis/src/base/objs/Cube/Cube.h +++ b/isis/src/base/objs/Cube/Cube.h @@ -155,6 +155,7 @@ namespace Isis { * an IsisPreference file cannot be found. Fixes #5145. * @history 2018-11-16 Jesse Mapel - Made several methods virtual for mocking. * @history 2019-06-15 Kristin Berry - Added latLonRange method to return the valid lat/lon rage of the cube. The values in the mapping group are not sufficiently accurate for some purposes. + * @history 2021-02-17 Jesse Mapel - Added hasBlob method to check for any type of BLOB. */ class Cube { public: @@ -299,6 +300,7 @@ namespace Isis { PvlGroup &group(const QString &group) const; bool hasGroup(const QString &group) const; bool hasTable(const QString &name); + bool hasBlob(const QString &type, const QString &name); void putGroup(const PvlGroup &group); void latLonRange(double &minLatitude, double &maxLatitude, double &minLongitude, double &maxLongitude); diff --git a/isis/src/base/objs/LinearAlgebra/LinearAlgebra.cpp b/isis/src/base/objs/LinearAlgebra/LinearAlgebra.cpp index 3567e61f40..7f964df0a0 100644 --- a/isis/src/base/objs/LinearAlgebra/LinearAlgebra.cpp +++ b/isis/src/base/objs/LinearAlgebra/LinearAlgebra.cpp @@ -20,6 +20,9 @@ find files of those names at the top level of this repository. **/ #include #include +// Armadillo +#include + // other Isis library #include "Angle.h" #include "Constants.h" @@ -29,9 +32,9 @@ find files of those names at the top level of this repository. **/ namespace Isis { /** - * Default constructor for a LinearAlgebra object. This is protected so it can - * never be constructed. The static methods and typedefs in this class are - * meant to be used without a LinearAlgebra object. + * Default constructor for a LinearAlgebra object. This is protected so it can + * never be constructed. The static methods and typedefs in this class are + * meant to be used without a LinearAlgebra object. */ LinearAlgebra::LinearAlgebra() { } @@ -44,15 +47,15 @@ namespace Isis { } - /** - * Determines whether the given matrix is the identity. Non-square matrices - * always return false. A qfuzzycompare is used when checking the zeroes and - * ones. - * - * @param matrix The matrix to check. Automatically returns false if not + /** + * Determines whether the given matrix is the identity. Non-square matrices + * always return false. A qfuzzycompare is used when checking the zeroes and + * ones. + * + * @param matrix The matrix to check. Automatically returns false if not * square. - * - * @return @b bool Returns true if the matrix is a square identity matrix + * + * @return @b bool Returns true if the matrix is a square identity matrix * (i.e. there are ones down the diagonal and zeroes elsewhere). */ bool LinearAlgebra::isIdentity(const Matrix &matrix) { @@ -74,13 +77,13 @@ namespace Isis { } - /** - * Determines whether the given matrix is orthogonal by verifying - * that the matrix and its tranpose are inverses. - * - * @param matrix The matrix to check. Automatically returns false + /** + * Determines whether the given matrix is orthogonal by verifying + * that the matrix and its tranpose are inverses. + * + * @param matrix The matrix to check. Automatically returns false * if not square. - * + * * @return @b bool Returns true if the matrix is orthogonal. */ bool LinearAlgebra::isOrthogonal(const Matrix &matrix) { @@ -94,18 +97,18 @@ namespace Isis { } - /** - * Determines whether the given matrix is a rotation matrix. - * - * @param matrix The matrix to check. Automatically returns false if not 2x2 + /** + * Determines whether the given matrix is a rotation matrix. + * + * @param matrix The matrix to check. Automatically returns false if not 2x2 * or 3x3. - * - * @return @b bool Returns true if the given matrix represents a rotation. - * - * @throw IException::Programmer "Unable to determine whether + * + * @return @b bool Returns true if the given matrix represents a rotation. + * + * @throw IException::Programmer "Unable to determine whether * the given matrix is a rotation matrix." - * - */ + * + */ // derived from naif's isrot routine bool LinearAlgebra::isRotationMatrix(const Matrix &matrix) { // rotation matrices must be square @@ -117,47 +120,47 @@ namespace Isis { * right-handed, orthonormal basis in 3-dimensional space. The * converse is true: all 3x3 matrices with this property are * rotation matrices. - * + * * An ordered set of three vectors V1, V2, V3 forms a right-handed, * orthonormal basis if and only if - * + * * 1) || V1 || = || V2 || = || V3 || = 1 - * + * * 2) V3 = V1 x V2. Since V1, V2, and V3 are unit vectors, * we also have - * + * * < V3, V1 x V2 > = 1. - * + * * This quantity is the determinant of the matrix whose * colums are V1, V2 and V3. - * + * * When finite precision numbers are used, rotation matrices will * usually fail to satisfy these criteria exactly. We must use * criteria that indicate approximate conformance to the criteria * listed above. We choose - * + * * 1) | || Vi || - 1 | < NTOL, i = 1, 2, 3. * - - * + * * 2) Let - * + * * Vi * Ui = ------ , i = 1, 2, 3. * ||Vi|| - * + * * Then we require - * + * * | < U3, U1 x U2 > - 1 | < DTOL; * - - * + * * equivalently, letting U be the matrix whose columns * are U1, U2, and U3, we insist on - * + * * | det(U) - 1 | < DTOL. * _ * The columns of M must resemble unit vectors. If the norms are * outside of the allowed range, M is not a rotation matrix. - * + * * Also, the columns of M are required to be pretty nearly orthogonal. The * discrepancy is gauged by taking the determinant of the matrix UNIT, * computed below, whose columns are the unitized columns of M. @@ -182,7 +185,7 @@ namespace Isis { } try { - // get the determinant of the unitized matrix and if it is not near 1, + // get the determinant of the unitized matrix and if it is not near 1, // this is not a rotation matrix double det = determinant(unitMatrix); if ( det >= 0.9 && det <= 1.1 ) { @@ -199,10 +202,10 @@ namespace Isis { /** - * Determines whether the given matrix is filled with zereos. - * + * Determines whether the given matrix is filled with zereos. + * * @param vector The matrix to check. - * + * * @return @b bool Returns true if the matrix is all zereos. */ bool LinearAlgebra::isZero(const Matrix &matrix) { @@ -220,9 +223,9 @@ namespace Isis { /** * Determines whether the given vector is filled with zereos. - * + * * @param vector The vector to check. - * + * * @return @b bool Returns true if the vector is all zereos. */ bool LinearAlgebra::isZero(const Vector &vector) { @@ -237,12 +240,12 @@ namespace Isis { /** - * Determines whether the given vector is empty (i.e. size 0). - * + * Determines whether the given vector is empty (i.e. size 0). + * * @param vector The vector to check. - * + * * @return @b bool Returns true if the size of the given vector is zero. - * + * */ bool LinearAlgebra::isEmpty(const LinearAlgebra::Vector &vector) { return vector.empty(); @@ -250,10 +253,10 @@ namespace Isis { /** - * Determines whether the given vector is a unit vector. - * + * Determines whether the given vector is a unit vector. + * * @param vector The vector to check. - * + * * @return @b bool Returns true if the vector is a unit vector. */ bool LinearAlgebra::isUnit(const Vector &vector) { @@ -265,16 +268,16 @@ namespace Isis { /** - * Returns the inverse of a 2x2 or 3x3 matrix. Throws an error if the given - * matrix is not invertible. - * + * Returns the inverse of a 2x2 or 3x3 matrix. Throws an error if the given + * matrix is not invertible. + * * @param matrix The matrix to inverse. - * + * * @return @b LinearAlgebra::Matrix The inverse matrix. - * - * @throw IException::Programmer "The given matrix is not invertible. + * + * @throw IException::Programmer "The given matrix is not invertible. * The determinant is 0.0." - * @throw IException::Programmer "Unable to invert the given matrix." + * @throw IException::Programmer "Unable to invert the given matrix." */ LinearAlgebra::Matrix LinearAlgebra::inverse(const Matrix &matrix) { try { @@ -290,31 +293,31 @@ namespace Isis { QString msg = "The given matrix is not invertible. The determinant is 0.0."; throw IException(IException::Programmer, msg, _FILEINFO_); } - + // since the determinant is not zero, we can calculate the reciprocal double scale = 1 / det; - + LinearAlgebra::Matrix inverse = identity(matrix.size1()); // find the inverse for 2x2 if (matrix.size1() == 2) { - inverse(0, 0) = scale * matrix(1, 1); - inverse(0, 1) = -scale * matrix(0, 1); - - inverse(1, 0) = -scale * matrix(1, 0); + inverse(0, 0) = scale * matrix(1, 1); + inverse(0, 1) = -scale * matrix(0, 1); + + inverse(1, 0) = -scale * matrix(1, 0); inverse(1, 1) = scale * matrix(0, 0); return inverse; } // else we have a 3x3 - inverse(0, 0) = scale * ( matrix(1, 1) * matrix(2, 2) - matrix(2, 1) * matrix(1, 2) ); - inverse(0, 1) = scale * ( matrix(0, 2) * matrix(2, 1) - matrix(2, 2) * matrix(0, 1) ); - inverse(0, 2) = scale * ( matrix(0, 1) * matrix(1, 2) - matrix(1, 1) * matrix(0, 2) ); - - inverse(1, 0) = scale * ( matrix(1, 2) * matrix(2, 0) - matrix(2, 2) * matrix(1, 0) ); + inverse(0, 0) = scale * ( matrix(1, 1) * matrix(2, 2) - matrix(2, 1) * matrix(1, 2) ); + inverse(0, 1) = scale * ( matrix(0, 2) * matrix(2, 1) - matrix(2, 2) * matrix(0, 1) ); + inverse(0, 2) = scale * ( matrix(0, 1) * matrix(1, 2) - matrix(1, 1) * matrix(0, 2) ); + + inverse(1, 0) = scale * ( matrix(1, 2) * matrix(2, 0) - matrix(2, 2) * matrix(1, 0) ); inverse(1, 1) = scale * ( matrix(0, 0) * matrix(2, 2) - matrix(2, 0) * matrix(0, 2) ); - inverse(1, 2) = scale * ( matrix(0, 2) * matrix(0, 1) - matrix(1, 2) * matrix(0, 0) ); - - inverse(2, 0) = scale * ( matrix(1, 0) * matrix(2, 1) - matrix(2, 0) * matrix(1, 1) ); - inverse(2, 1) = scale * ( matrix(0, 1) * matrix(2, 0) - matrix(2, 1) * matrix(0, 0) ); + inverse(1, 2) = scale * ( matrix(0, 2) * matrix(0, 1) - matrix(1, 2) * matrix(0, 0) ); + + inverse(2, 0) = scale * ( matrix(1, 0) * matrix(2, 1) - matrix(2, 0) * matrix(1, 1) ); + inverse(2, 1) = scale * ( matrix(0, 1) * matrix(2, 0) - matrix(2, 1) * matrix(0, 0) ); inverse(2, 2) = scale * ( matrix(0, 0) * matrix(1, 1) - matrix(1, 0) * matrix(0, 1) ); return inverse; @@ -326,11 +329,41 @@ namespace Isis { } + /** + * Returns the pseudoinverse of a matrix. + * + * @param matrix The matrix to compute the pseudoinverse of. + * + * @return @b LinearAlgebra::Matrix The pseudoinverse matrix. + */ + LinearAlgebra::Matrix LinearAlgebra::pseudoinverse(const Matrix &matrix) { + // Copy values into Armadillo matrix + arma::mat arMat(matrix.size1(), matrix.size2()); + for (size_t i = 0; i < matrix.size1(); i++) { + for (size_t j = 0; j < matrix.size2(); j++) { + arMat(i, j) = matrix(i, j); + } + } + + arma::mat invArMat = arma::pinv(arMat); + + // Copy values back to Boost matrix + LinearAlgebra::Matrix inverse(invArMat.n_rows, invArMat.n_cols); + for (size_t i = 0; i < invArMat.n_rows; i++) { + for (size_t j = 0; j < invArMat.n_cols; j++) { + inverse(i, j) = invArMat(i, j); + } + } + + return inverse; + } + + /** * Returns the transpose of the given matrix. - * + * * @param matrix The matrix to transpose. - * + * * @return @b LinearAlgebra::Matrix The transposed matrix. */ LinearAlgebra::Matrix LinearAlgebra::transpose(const Matrix &matrix) { @@ -341,16 +374,16 @@ namespace Isis { /** * Returns the identity matrix of size NxN. - * + * * @param int The size of the square matrix. - * + * * @return @b LinearAlgebra::Matrix The NxN square matrix. - * + * * @throw IException::Programmer "Can not create identity matrix of negative size." */ LinearAlgebra::Matrix LinearAlgebra::identity(int size) { if (size < 1) { - QString msg = "Can not create identity matrix of negative size [" + QString msg = "Can not create identity matrix of negative size [" + toString((int) size) + "]."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -364,12 +397,12 @@ namespace Isis { } - /** + /** * Returns a matrix with given dimensions that is filled with zeroes. - * + * * @param rows The number of rows in the returned matrix. * @param columns The number of colums in the returned matrix. - * + * * @return @b LinearAlgebra::Matrix A zero-filled matrix. */ LinearAlgebra::Matrix LinearAlgebra::zeroMatrix(int rows, int columns) { @@ -378,11 +411,11 @@ namespace Isis { } - /** + /** * Returns a vector of given length that is filled with zeroes. - * + * * @param size Size of the vector. - * + * * @return @b LinearAlgebra::Vector A zero-filled vector. */ LinearAlgebra::Vector LinearAlgebra::zeroVector(int size) { @@ -392,23 +425,23 @@ namespace Isis { /** - * Returns the determinant of the given 3x3 matrix. - * - * @param LinearAlgebra::Matrix The 3x3 matrix whose determinant will be + * Returns the determinant of the given 3x3 matrix. + * + * @param LinearAlgebra::Matrix The 3x3 matrix whose determinant will be * calculated. - * + * * @return @b double The determinant of the given matrix. - * - * @throw IException::Programmer "Unable to calculate the determinant for the + * + * @throw IException::Programmer "Unable to calculate the determinant for the * given matrix. This method only calculates the * determinant for 2x2 or 3x3 matrices." - * + * */ double LinearAlgebra::determinant(const Matrix &matrix) { if ( (matrix.size1() != matrix.size2()) || (matrix.size1() != 2 && matrix.size1() != 3) ) { QString msg = "Unable to calculate the determinant for the given matrix. " "This method only calculates the determinant for 2x2 or 3x3 matrices." - "The given matrix is [" + toString((int) matrix.size1()) + "x" + "The given matrix is [" + toString((int) matrix.size1()) + "x" + toString((int) matrix.size2()) + "]."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -418,27 +451,27 @@ namespace Isis { } else { - return matrix(0, 0) * (matrix(1, 1)*matrix(2, 2) - matrix(1, 2)*matrix(2, 1)) - - matrix(0, 1) * (matrix(1, 0)*matrix(2, 2) - matrix(1, 2)*matrix(2, 0)) - + matrix(0, 2) * (matrix(1, 0)*matrix(2, 1) - matrix(1, 1)*matrix(2, 0)); + return matrix(0, 0) * (matrix(1, 1)*matrix(2, 2) - matrix(1, 2)*matrix(2, 1)) + - matrix(0, 1) * (matrix(1, 0)*matrix(2, 2) - matrix(1, 2)*matrix(2, 0)) + + matrix(0, 2) * (matrix(1, 0)*matrix(2, 1) - matrix(1, 1)*matrix(2, 0)); } } /** - * Returns a unit vector that is codirectional with the given + * Returns a unit vector that is codirectional with the given * vector by dividing each component of the vector by the vector magnitude. - * - * @f$ \hat{v} = \frac{v}{\| v \|} @f$ - * where @f$ \| u \|_ @f$ is the magnitude of u. - * - * @see magnitude() - * + * + * @f$ \hat{v} = \frac{v}{\| v \|} @f$ + * where @f$ \| u \|_ @f$ is the magnitude of u. + * + * @see magnitude() + * * @param vector The vector to be normalized. - * + * * @return @b LinearAlgebra::Vector A unit vector from the given vector. - * + * * @throw IException::Programmer "Unable to normalize the zero vector." */ LinearAlgebra::Vector LinearAlgebra::normalize(const Vector &vector) { @@ -455,17 +488,17 @@ namespace Isis { * Computes the magnitude (i.e., the length) of the given * vector using the Euclidean norm (L2 norm). The maximum norm * (L-infinity) is also used to stabilize the solution in order to avoid - * overflow. This method uses the computation - * - * @f$ \| v \| = \| v \|_\infty \| \frac{v}{\| v \|_\infty} \|_2 @f$ - * where @f$ \| u \|_\infty = \max_{i=1}^n \{ \mid u_i \mid \} @f$ is the maximum norm - * and @f$ \| u \|_2 = \sqrt{\sum_{i=1}^n u_1^2} @f$ is the Euclidean norm. - * - * - * @see absoluteMaximum() - * + * overflow. This method uses the computation + * + * @f$ \| v \| = \| v \|_\infty \| \frac{v}{\| v \|_\infty} \|_2 @f$ + * where @f$ \| u \|_\infty = \max_{i=1}^n \{ \mid u_i \mid \} @f$ is the maximum norm + * and @f$ \| u \|_2 = \sqrt{\sum_{i=1}^n u_1^2} @f$ is the Euclidean norm. + * + * + * @see absoluteMaximum() + * * @param vector The vector whose magnitude will be computed. - * + * * @return @b double The magnitude (length) of the given vector. */ double LinearAlgebra::magnitude(const Vector &vector) { @@ -484,15 +517,15 @@ namespace Isis { /** - * Returns the maximum norm (L-infinity norm) for the given vector. - * - * The maximum norm is defined by the absolute values of the vector - * components: - * - * @f$ \| v \|_\infty = \max_{i=1}^n \{ \mid v_i \mid \} @f$ - * + * Returns the maximum norm (L-infinity norm) for the given vector. + * + * The maximum norm is defined by the absolute values of the vector + * components: + * + * @f$ \| v \|_\infty = \max_{i=1}^n \{ \mid v_i \mid \} @f$ + * * @param vector The vector whose absolute maximum will be returned. - * + * * @return @b double The maximum of the absolute values of the vector components. */ double LinearAlgebra::absoluteMaximum(const Vector &vector) { @@ -500,25 +533,25 @@ namespace Isis { } - /** + /** * Returns the product of two matrices. Will throw an error if the matrices * are not properly sized (the number of columns of the first matrix must * match the number or rows of the second matrix). - * + * * @param matrix1 The left matrix. * @param matrix2 The right matrix. - * + * * @return @b LinearAlgebra::Matrix The resultant matrix product. - * - * @throw IException::Programmer "Unable to multiply matrices + * + * @throw IException::Programmer "Unable to multiply matrices * with mismatched dimensions." */ LinearAlgebra::Matrix LinearAlgebra::multiply(const Matrix &matrix1, const Matrix &matrix2) { // Check to make sure we can multiply if (matrix1.size2() != matrix2.size1()) { QString msg = "Unable to multiply matrices with mismatched dimensions. " - "The left matrix has [" + toString((int) matrix1.size2()) - + "] columns and the right matrix has [" + toString((int) matrix2.size1()) + "The left matrix has [" + toString((int) matrix1.size2()) + + "] columns and the right matrix has [" + toString((int) matrix2.size1()) + "] rows."; throw IException(IException::Programmer, msg, _FILEINFO_); @@ -530,26 +563,26 @@ namespace Isis { /** - * Computes the product of the given matrix and vector. The vector will - * be multiplied on the right side of the given matrix. Will throw an - * error if the two are not properly sized (matrix columns not equal to + * Computes the product of the given matrix and vector. The vector will + * be multiplied on the right side of the given matrix. Will throw an + * error if the two are not properly sized (matrix columns not equal to * vector size). - * + * * @param matrix The matrix to be multiplied. - * @param vector The column vector to be multiplied on the right side of + * @param vector The column vector to be multiplied on the right side of * the matrix. - * + * * @return @b LinearAlgebra::Vector The resultant vector. - * - * @throw IException::Programmer "Unable to multiply matrix and vector + * + * @throw IException::Programmer "Unable to multiply matrix and vector * with mismatched dimensions." */ LinearAlgebra::Vector LinearAlgebra::multiply(const Matrix &matrix, const Vector &vector) { // Check to make sure we can multiply if (matrix.size2() != vector.size()) { QString msg = "Unable to multiply matrix and vector with mismatched dimensions." - "The given vector has [" + toString((int) vector.size()) - + "] components and the given matrix has [" + "The given vector has [" + toString((int) vector.size()) + + "] components and the given matrix has [" + toString((int) matrix.size2()) + "] columns."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -561,10 +594,10 @@ namespace Isis { /** * Multiplies the given scalar and vector. - * + * * @param scalar The scalar to be multiplied by each component of the vector. * @param vector The vector to be scaled. - * + * * @return @b LinearAlgebra::Vector The resultant scaled vector. */ LinearAlgebra::Vector LinearAlgebra::multiply(double scalar, const Vector &vector) { @@ -575,10 +608,10 @@ namespace Isis { /** * Multiplies the given scalar and matrix. - * + * * @param scalar The scalar to be multiplied by each element of the matrix. * @param matrix The matrix to be scaled. - * + * * @return @b LinearAlgebra::Matrix The resultant scaled matrix. */ LinearAlgebra::Matrix LinearAlgebra::multiply(double scalar, const Matrix &matrix) { @@ -589,21 +622,21 @@ namespace Isis { /** * Adds the two given vectors. - * + * * @param vector1 The first vector. * @param vector2 The second vector. - * + * * @return @b LinearAlgebra::Vector The sum of the vectors. - * - * @throw IException::Programmer "Unable to add vectors + * + * @throw IException::Programmer "Unable to add vectors * with mismatched sizes." */ LinearAlgebra::Vector LinearAlgebra::add(const Vector &vector1, const Vector &vector2) { // Vectors best be the same size if (vector1.size() != vector2.size()) { QString msg = "Unable to add vectors with mismatched sizes." - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -614,22 +647,22 @@ namespace Isis { /** * Subtracts the right vector from the left vector. - * + * * @param vector1 The vector to the left of the subtraction operator. * @param vector2 The vector to the right of the subtraction operator. - * - * @return @b LinearAlgebra::Vector The difference of the vectors + * + * @return @b LinearAlgebra::Vector The difference of the vectors * (i.e. vector1 - vector2). - * - * @throw IException::Programmer "Unable to subtract vectors + * + * @throw IException::Programmer "Unable to subtract vectors * with mismatched sizes." */ LinearAlgebra::Vector LinearAlgebra::subtract(const Vector &vector1, const Vector &vector2) { // Vectors best be the same size if (vector1.size() != vector2.size()) { QString msg = "Unable to subtract vectors with mismatched sizes." - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -641,22 +674,22 @@ namespace Isis { /** * Returns the cross product of two vectors. Note: the cross-product * requires the vectors to have exactly three components each. - * + * * @param vector1 The vector to the left of the cross product operator. * @param vector2 The vector to the right of the cross product operator. - * - * @return @b LinearAlgebra::Vector The cross product of the given + * + * @return @b LinearAlgebra::Vector The cross product of the given * vectors (i.e. vector1 x vector2). - * - * @throw IException::Programmer "Unable to calculate the cross + * + * @throw IException::Programmer "Unable to calculate the cross * product on vectors that are not * size 3. " */ LinearAlgebra::Vector LinearAlgebra::crossProduct(const Vector &vector1, const Vector &vector2) { if ((vector1.size() != 3) || (vector2.size() != 3)) { QString msg = "Unable to calculate the cross product on vectors that are not size 3. " - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -671,21 +704,21 @@ namespace Isis { /** - * Divides each vector by its corresponding absolute maximum, + * Divides each vector by its corresponding absolute maximum, * computes the cross product of the new vectors, and normalizes the - * resultant vector from the cross product. Note: the - * cross-product requires the vectors to have exactly three - * components each. - * + * resultant vector from the cross product. Note: the + * cross-product requires the vectors to have exactly three + * components each. + * * @param vector1 The vector to the left of the cross product operator. * @param vector2 The vector to the right of the cross product operator. - * - * @return @b LinearAlgebra::Vector The normalized cross product of the given vectors - * (i.e. normalize(vector1/absoluteMaximum(vector1) x - * vector2/absoluteMaximum(vector2))). + * + * @return @b LinearAlgebra::Vector The normalized cross product of the given vectors + * (i.e. normalize(vector1/absoluteMaximum(vector1) x + * vector2/absoluteMaximum(vector2))). */ // is this derived from naif's ucrss routine??? - LinearAlgebra::Vector LinearAlgebra::normalizedCrossProduct(const Vector &vector1, + LinearAlgebra::Vector LinearAlgebra::normalizedCrossProduct(const Vector &vector1, const Vector &vector2) { double maxVector1 = LinearAlgebra::absoluteMaximum(vector1); @@ -708,24 +741,24 @@ namespace Isis { } - /** - * Computes the outer product of the given vectors. The outer product - * operation is defined as the cross product of vector1 and the conjugate + /** + * Computes the outer product of the given vectors. The outer product + * operation is defined as the cross product of vector1 and the conjugate * transpose of vector2. - * + * * @param vector1 The vector to the left side of the outer product operator. * @param vector2 The vector to the right side of the outer product operator. - * + * * @return @b LinearAlgebra::Matrix The outer product matrix of the given vectors. - * - * @throw IException::Programmer "Unable to compute the outer product for + * + * @throw IException::Programmer "Unable to compute the outer product for * vectors with mismatched sizes." - */ + */ LinearAlgebra::Matrix LinearAlgebra::outerProduct(const Vector &vector1, const Vector &vector2) { if (vector1.size() != vector2.size()) { QString msg = "Unable to compute the outer product for vectors with mismatched sizes." - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -734,16 +767,16 @@ namespace Isis { /** - * Computes the dot product of the given vectors. For Euclidean space, this is the same + * Computes the dot product of the given vectors. For Euclidean space, this is the same * as the inner product, so these methods are interchangeable. * - * @see innerProduct() - * + * @see innerProduct() + * * @param vector1 The first vector. * @param vector2 The second vector. - * + * * @return @b double The dot product of the vectors. - * + * */ double LinearAlgebra::dotProduct(const Vector &vector1, const Vector &vector2) { // no error check needed - this is done by innerProduct @@ -751,25 +784,25 @@ namespace Isis { } - /** - * Computes the inner product of the given vectors. For Euclidean space, this is the same - * as the dot product, so these methods are interchangeable. + /** + * Computes the inner product of the given vectors. For Euclidean space, this is the same + * as the dot product, so these methods are interchangeable. * * @see dotProduct() * * @param vector1 The first vector. * @param vector2 The second vector. - * + * * @return @b double The inner product of the vectors. - * - * @throw IException::Programmer "Unable to compute the dot product for vectors + * + * @throw IException::Programmer "Unable to compute the dot product for vectors * with mismatched sizes." */ double LinearAlgebra::innerProduct(const Vector &vector1, const Vector &vector2) { if (vector1.size() != vector2.size()) { QString msg = "Unable to compute the dot product for vectors with mismatched sizes." - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -777,36 +810,36 @@ namespace Isis { } - /** - * Compute the vector projection of vector1 onto vector2. This is - * the orthogonal projection of vector1 onto a line that is parallel - * to vector2. - * - * It is defined by - * @f$ proj_{v_2} v_1 = \frac{v_1 \cdot v_2}{\| v_2 \|^2}v_2 @f$ - * where @f$ \| u \|_ @f$ is the magnitude of u. - * + /** + * Compute the vector projection of vector1 onto vector2. This is + * the orthogonal projection of vector1 onto a line that is parallel + * to vector2. + * + * It is defined by + * @f$ proj_{v_2} v_1 = \frac{v_1 \cdot v_2}{\| v_2 \|^2}v_2 @f$ + * where @f$ \| u \|_ @f$ is the magnitude of u. + * * @param vector1 The vector to the left of the project operator. * @param vector2 The vector to the right of the project operator. - * - * @return @b LinearAlgebra::Vector The resultant vector that is + * + * @return @b LinearAlgebra::Vector The resultant vector that is * the orthogonal projection of * vector1 onto vector2. - * - * @throw IException::Programmer "Unable to project vector1 onto vector2 + * + * @throw IException::Programmer "Unable to project vector1 onto vector2 * with mismatched sizes." - */ + */ // derived from naif's vproj routine LinearAlgebra::Vector LinearAlgebra::project(const Vector &vector1, const Vector &vector2) { if (vector1.size() != vector2.size()) { QString msg = "Unable to project vector1 onto vector2 with mismatched sizes." - "Vector1 has [" + toString((int) vector1.size()) - + "] components and vector2 has [" + "Vector1 has [" + toString((int) vector1.size()) + + "] components and vector2 has [" + toString((int) vector2.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } - // if vector2 is the zero vector, then the projection of vector1 onto vector2 + // if vector2 is the zero vector, then the projection of vector1 onto vector2 // is also the zero vector if (LinearAlgebra::isZero(vector2)) return vector2; @@ -821,28 +854,28 @@ namespace Isis { /** - * Rotates a vector about an axis vector given a specified angle. - * Note: this method only rotates a vector with three components since - * the cross product calculation requires this. - * + * Rotates a vector about an axis vector given a specified angle. + * Note: this method only rotates a vector with three components since + * the cross product calculation requires this. + * * @param vector The vector to rotate, which must have three components. * @param axis A vector defining the axis, which must also have three components. * @param angle The angle to rotate. - * + * * @return @b LinearAlgebra::Vector The rotated vector. - * - * @throw IException::Programmer "Unable to rotate vector about the + * + * @throw IException::Programmer "Unable to rotate vector about the * given axis and angle. Vectors must * be of size 3 to perform rotation. " */ // derived from naif's vrotv routine - LinearAlgebra::Vector LinearAlgebra::rotate(const Vector &vector, const Vector &axis, + LinearAlgebra::Vector LinearAlgebra::rotate(const Vector &vector, const Vector &axis, Angle angle) { if ((vector.size() != 3) || (axis.size() != 3)) { QString msg = "Unable to rotate vector about the given axis and angle. " "Vectors must be of size 3 to perform rotation. " - "The given vector has [" + toString((int) vector.size()) - + "] components and the given axis has [" + "The given vector has [" + toString((int) vector.size()) + + "] components and the given axis has [" + toString((int) axis.size()) + "] components."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -854,19 +887,19 @@ namespace Isis { // Compute the unit vector that is codirectional with the given axis Vector axisUnitVector = normalize(axis); - // Compute the projection of the given vector onto the axis unit vector. + // Compute the projection of the given vector onto the axis unit vector. Vector projVectorOnAxis = project(vector, axisUnitVector); // Compute the component of the input orthogonal to the AXIS. Call it V1. Vector v1 = vector - projVectorOnAxis; - + // Rotate V1 by 90 degrees about the AXIS and call the result V2. Vector v2 = LinearAlgebra::crossProduct(axisUnitVector, v1); - + // Compute cos(angle)*v1 + sin(angle)*v2. This is v1 rotated about // the axis in the plane normal to the axis, call the result rplane double c = cos(angle.radians()); - double s = sin(angle.radians()); + double s = sin(angle.radians()); Vector rplane = (c*v1) + (s*v2); // Add the rotated component in the normal plane to axis to the @@ -879,20 +912,20 @@ namespace Isis { * Finds the unique vector P such that A = V + P, V is * parallel to B and P is perpendicular to B, where A is the first vector * (vector1) and B is the second vector (vector2) passed in by the user. - * - * For all vectors A and B, there exists unique vectors V and P such that + * + * For all vectors A and B, there exists unique vectors V and P such that * 1) A = V + P - * 2) V is parallel to B + * 2) V is parallel to B * 3) P is perpendicular to B - * + * * @param vector1 The first vector, denoted A in the description. * @param vector2 The second vector, denoted B in the description. - * + * * @return @b LinearAlgebra::Vector A vector perpendicular to vector2 and * equal to vector1-parallel2 (where * parallel2 is some vector that is * parallel to vector2). - */ + */ // Derived from NAIF's vperp routine // possible that we may need to restrict size to dimesion 3, but doesn't seem // necessary in the code. @@ -920,18 +953,18 @@ namespace Isis { } - /** - * Converts a rotation's representation from a matrix to a axis of rotation - * and its corresponding rotation angle. - * + /** + * Converts a rotation's representation from a matrix to a axis of rotation + * and its corresponding rotation angle. + * * @param rotationMatrix A matrix representing a rotation. - * + * * @return @b LinearAlgebra::AxisAngle The axis-angle pair representing the rotation. - * - * @throw IException::Programmer "Unable to convert the given matrix to an + * + * @throw IException::Programmer "Unable to convert the given matrix to an * axis of rotation and a rotation angle. A * 3x3 matrix is required." - * @throw IException::Programmer "Unable to convert the given matrix to an + * @throw IException::Programmer "Unable to convert the given matrix to an * axis of rotation and a rotation angle. * The given matrix is not a rotation * matrix." @@ -941,8 +974,8 @@ namespace Isis { if ((rotationMatrix.size1() != 3) || (rotationMatrix.size2() != 3)) { QString msg = "Unable to convert the given matrix to an axis of rotation " - "and a rotation angle. A 3x3 matrix is required. The given matrix is [" - + toString((int) rotationMatrix.size1()) + "x" + "and a rotation angle. A 3x3 matrix is required. The given matrix is [" + + toString((int) rotationMatrix.size1()) + "x" + toString((int) rotationMatrix.size2()) + "]."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -953,7 +986,7 @@ namespace Isis { "and a rotation angle. The given matrix is not a rotation matrix."; throw IException(IException::Programmer, msg, _FILEINFO_); } - + Angle angle; Vector axis(3); @@ -987,59 +1020,59 @@ namespace Isis { axis = LinearAlgebra::normalize(subQuaternion); angle.setRadians(2.0 * atan2(magnitude(subQuaternion), quaternion(0))); } - + return qMakePair(axis, angle); } /** * Converts a rotation's representation from a matrix to a set of Euler angles - * with corresponding axes. - * + * with corresponding axes. + * * @param rotationMatrix A matrix representing a rotation. * @param axes A list containing the order of axes. - * - * @return @b QList< LinearAlgebra::EulerAngle> The list of 3 Euler angles + * + * @return @b QList< LinearAlgebra::EulerAngle> The list of 3 Euler angles * with their axes representing the rotation. - * - * @throw IException::Programmer "Unable to convert the given matrix to Euler angles. + * + * @throw IException::Programmer "Unable to convert the given matrix to Euler angles. * Exactly 3 axis codes are required." - * @throw IException::Programmer "Unable to convert the given matrix to Euler angles + * @throw IException::Programmer "Unable to convert the given matrix to Euler angles * using the given axis codes. Axis codes must be 1, 2, or 3." - * @throw IException::Programmer "Unable to convert the given matrix to Euler angles + * @throw IException::Programmer "Unable to convert the given matrix to Euler angles * using the given axis codes. The middle axis * code must differ from its neighbors." - * @throw IException::Programmer "Unable to convert the given matrix to Euler angles. + * @throw IException::Programmer "Unable to convert the given matrix to Euler angles. * A 3x3 matrix is required." * @throw IException::Programmer "Unable to convert the given matrix to Euler angles. * The given matrix is not a rotation matrix." */ // Derived from NAIF's m2eul routine - QList LinearAlgebra::toEulerAngles(const Matrix &rotationMatrix, + QList LinearAlgebra::toEulerAngles(const Matrix &rotationMatrix, const QList axes) { // check there are 3 axes in the set {1,2,3} with center axis not equal to first or last if (axes.size() != 3) { QString msg = "Unable to convert the given matrix to Euler angles. " - "Exactly 3 axis codes are required. The given list has [" + "Exactly 3 axis codes are required. The given list has [" + toString((int) axes.size()) + "] axes."; throw IException(IException::Programmer, msg, _FILEINFO_); } QSet validAxes; validAxes << 1 << 2 << 3; - if (!validAxes.contains(axes[0]) - || !validAxes.contains(axes[1]) + if (!validAxes.contains(axes[0]) + || !validAxes.contains(axes[1]) || !validAxes.contains(axes[2])) { QString msg = "Unable to convert the given matrix to Euler angles using the given axis codes " - "[" + toString(axes[0]) + ", " + toString(axes[1]) + ", " + toString(axes[2]) + "[" + toString(axes[0]) + ", " + toString(axes[1]) + ", " + toString(axes[2]) + "]. Axis codes must be 1, 2, or 3."; throw IException(IException::Programmer, msg, _FILEINFO_); } if (axes[0] == axes[1] || axes[1] == axes[2]) { QString msg = "Unable to convert the given matrix to Euler angles using the given axis codes " - "[" + toString(axes[0]) + ", " + toString(axes[1]) + ", " + toString(axes[2]) + "[" + toString(axes[0]) + ", " + toString(axes[1]) + ", " + toString(axes[2]) + "]. The middle axis code must differ from its neighbors."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1047,8 +1080,8 @@ namespace Isis { // check the matrix is 3x3 rotation if ((rotationMatrix.size1() != 3) || (rotationMatrix.size2() != 3)) { QString msg = "Unable to convert the given matrix to Euler angles. A 3x3 matrix is required. " - "The given matrix is [" - + toString((int) rotationMatrix.size1()) + "x" + "The given matrix is [" + + toString((int) rotationMatrix.size1()) + "x" + toString((int) rotationMatrix.size2()) + "]."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1095,9 +1128,9 @@ namespace Isis { LinearAlgebra::Matrix tempMatrix = multiply( tempRotation, change ); tempRotation = multiply( transpose(change), tempMatrix ); - bool degen = ( qFuzzyCompare(tempRotation(0, 2) + 1.0, 1.0) + bool degen = ( qFuzzyCompare(tempRotation(0, 2) + 1.0, 1.0) && qFuzzyCompare(tempRotation(1, 2) + 1.0, 1.0) ) - || ( qFuzzyCompare(tempRotation(2, 0) + 1.0, 1.0) + || ( qFuzzyCompare(tempRotation(2, 0) + 1.0, 1.0) && qFuzzyCompare(tempRotation(2, 1) + 1.0, 1.0) ) || qFuzzyCompare( qAbs(tempRotation(2, 2)), 1.0 ); @@ -1106,7 +1139,7 @@ namespace Isis { angle3.setRadians( 0.0 ); angle2.setRadians( acos( tempRotation(2, 2) ) ); angle1.setRadians( atan2( tempRotation(0, 1), tempRotation(0, 0) ) ); - + } else { // the normal case. @@ -1132,9 +1165,9 @@ namespace Isis { change( axes[2] - 1, 2 ) = sign * 1.0; LinearAlgebra::Matrix tempMatrix = multiply( tempRotation, change ); tempRotation = multiply( transpose(change), tempMatrix ); - bool degen = ( qFuzzyCompare(tempRotation(0, 0) + 1.0, 1.0) + bool degen = ( qFuzzyCompare(tempRotation(0, 0) + 1.0, 1.0) && qFuzzyCompare(tempRotation(0, 1) + 1.0, 1.0) ) - || ( qFuzzyCompare(tempRotation(1, 2) + 1.0, 1.0) + || ( qFuzzyCompare(tempRotation(1, 2) + 1.0, 1.0) && qFuzzyCompare(tempRotation(2, 2) + 1.0, 1.0) ) || qFuzzyCompare( qAbs(tempRotation(0, 2)), 1.0 ); @@ -1154,20 +1187,20 @@ namespace Isis { } QList eulerAngles; - eulerAngles << qMakePair(angle3, axes[0]) - << qMakePair(angle2, axes[1]) + eulerAngles << qMakePair(angle3, axes[0]) + << qMakePair(angle2, axes[1]) << qMakePair(angle1, axes[2]); return eulerAngles; } /** - * Converts a rotation's representation from a matrix to a unit quaternion. - * + * Converts a rotation's representation from a matrix to a unit quaternion. + * * @param rotationMatrix A matrix representing a rotation. - * - * @return @b LinearAlgebra::Vector A unit quaternion representing the rotation. - * + * + * @return @b LinearAlgebra::Vector A unit quaternion representing the rotation. + * * @throw IException::Programmer "Unable to convert the given matrix to a quaternion. A 3x3 matrix is required. * @throw IException::Programmer "Unable to convert the given matrix to an axis of rotation @@ -1179,8 +1212,8 @@ namespace Isis { if ((rotationMatrix.size1() != 3) || (rotationMatrix.size2() != 3)) { QString msg = "Unable to convert the given matrix to a quaternion. " - "A 3x3 matrix is required. The given matrix is [" - + toString((int) rotationMatrix.size1()) + "x" + "A 3x3 matrix is required. The given matrix is [" + + toString((int) rotationMatrix.size1()) + "x" + toString((int) rotationMatrix.size2()) + "]."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1202,52 +1235,52 @@ namespace Isis { // snm = sn * sm // The rotation matrix corresponding to our quaternion is: - // + // // | cc+s11-s22-s33 2*s12-2*cs3 2*s13+2*cs2 | // | 2*s12+2*cs3 cc-s11+s22-s33 2*s23-2*cs1 | // | 2*s13-2*cs2 2*s23+2*cs1 cc-s11-s22+s33 | - // - // + // + // // since |q| = cc + s11 + s22 + s33 = 1, we can use substitution on the diagonal entries to get - // + // // | 1-2*s22-2*s33 2*s12-2*cs3 2*s13+2*cs2 | // | 2*s12+2*cs3 1-2*s11-2*s33 2*s23-2*cs1 | // | 2*s13-2*cs2 2*s23+2*cs1 1-2*s11-2*s22 | - // - // - // + // + // + // // r(1,1) = 1.0 - 2*s22 - 2*s33 // r(2,1) = 2*s12 + 2*cs3 // r(3,1) = 2*s13 - 2*cs2 - // + // // r(1,2) = 2*s12 - 2*cs3 // r(2,2) = 1.0 - 2*s11 - 2*s33 // r(3,2) = 2*s23 + 2*cs1 - // + // // r(1,3) = 2*s13 + 2*cs2 // r(2,3) = 2*s23 - 2*cs1 // r(3,3) = 1.0 - 2*s11 - 2*s22 - + // Using this matrix, we get the trace by summing the diagonal entries // trace = (1-2*s22-2*s33) + (1-2*s11-2*s33) + (1-2*s11-2*s22) // = 3 - 4*(s11 + s22 + s33) - // Therefore + // Therefore // 1.0 + trace = 4 - 4*(s11 + s22 + s33) // = 4*(1 - s11 - s22 - s33) by factoring 4 // = 4*(|q| - s11 - s22 - s33) since |q| = 1 // = 4*cc since |q| = cc + s11 + s22 + s33 - // + // // Solving for c, we get that // 4*cc = 1 + trace // 2*c = +/- sqrt(1 + trace) // c = +/- 0.5 * sqrt( 1.0 + trace ) - // We also have the following where {n,m,p} = {1,2,3} + // We also have the following where {n,m,p} = {1,2,3} // 1.0 + trace - 2.0*r(n,n) = 4.0 - 4.0(snn + smm + spp) - 2(1.0 - 2.0(smm + spp )) // = 4.0 - 4.0(snn + smm + spp) - 2.0 + 4.0(smm + spp ) // = 2.0 - 4.0*snn - // + // // Solving for snn, we get // 2.0 - 4.0*snn = 1.0 + trace - 2.0*r(n,n) // -2.0 + 4.0*snn = -1.0 - trace + 2.0*r(n,n) @@ -1261,13 +1294,13 @@ namespace Isis { // In addition to these observations, note that all of the product // pairs can easily be computed by simplifying the expressions // (r(n,m) - r(m,n)) / 4.0 and (r(n,m) + r(m,n)) / 4.0 - // + // // For example, // (r(3,2) - r(2,3)) / 4.0 = ( (2*s23 + 2*cs1) - (2*s23 - 2*cs1) ) / 4.0 // = ( 4*cs1 ) / 4.0 // = cs1 - // - // So we have the following + // + // So we have the following // cs1 = (r(3,2) - r(2,3))/4.0 // cs2 = (r(1,3) - r(3,1))/4.0 // cs3 = (r(2,1) - r(1,2))/4.0 @@ -1295,7 +1328,7 @@ namespace Isis { // Note that cc4 + s114 + s224 + s334 = 4|q| = 4 // Thus, at least one of the 4 terms is greater than 1. - double normalizingFactor; + double normalizingFactor; LinearAlgebra::Vector quaternion(4); if ( cc4 >= 1.0 ) { // true if the trace is non-negative @@ -1355,16 +1388,16 @@ namespace Isis { /** - * Converts a rotation's representation from an axis of rotation and its + * Converts a rotation's representation from an axis of rotation and its * corresponding rotation angle to a 3x3 matrix. - * - * + * + * * @param axis The axis of rotation for a rotation. * @param angle The rotation angle. - * + * * @return @b LinearAlgebra::Matrix The matrix representing the rotation. - * - * @throw IException::Programmer "Unable to convert the given vector and + * + * @throw IException::Programmer "Unable to convert the given vector and * angle to a rotation matrix. The given * vector is not a 3D axis vector." */ @@ -1372,7 +1405,7 @@ namespace Isis { LinearAlgebra::Matrix LinearAlgebra::toMatrix(const Vector &axis, Angle angle) { if (axis.size() != 3) { QString msg = "Unable to convert the given vector and angle to a rotation matrix. " - "The given vector with size [" + toString((int) axis.size()) + "The given vector with size [" + toString((int) axis.size()) + "] is not a 3D axis vector."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1398,13 +1431,13 @@ namespace Isis { /** - * Converts a rotation's representation from an axis of rotation and its + * Converts a rotation's representation from an axis of rotation and its * corresponding rotation angle to a 3x3 matrix. - * + * * @param axisAngle The axis-angle pair representation of a rotation - * + * * @return @b LinearAlgebra::Matrix The matrix representation of the rotation. - * + * */ LinearAlgebra::Matrix LinearAlgebra::toMatrix(const AxisAngle &axisAngle) { return LinearAlgebra::toMatrix(axisAngle.first, axisAngle.second); @@ -1412,30 +1445,30 @@ namespace Isis { /** - * Converts a rotation's representation from a set of Euler angles (3 angles, - * each with a corresponding axis) to a 3x3 matrix. - * + * Converts a rotation's representation from a set of Euler angles (3 angles, + * each with a corresponding axis) to a 3x3 matrix. + * * @param angle3 The third angle and its axis. * @param angle2 The second angle and its axis. * @param angle1 The first angle and its axis. - * + * * @return @b LinearAlgebra::Matrix The matrix representation of the rotation. - * - * @throw IException::Programmer "Unable to convert the given Euler angles to + * + * @throw IException::Programmer "Unable to convert the given Euler angles to * a matrix using the given axis codes. Axis * codes must be 1, 2, or 3." */ // Derived from NAIF's eul2m routine - LinearAlgebra::Matrix LinearAlgebra::toMatrix(const EulerAngle &angle3, - const EulerAngle &angle2, + LinearAlgebra::Matrix LinearAlgebra::toMatrix(const EulerAngle &angle3, + const EulerAngle &angle2, const EulerAngle &angle1) { QSet validAxes; validAxes << 1 << 2 << 3; - if (!validAxes.contains(angle3.second) - || !validAxes.contains(angle2.second) + if (!validAxes.contains(angle3.second) + || !validAxes.contains(angle2.second) || !validAxes.contains(angle1.second)) { QString msg = "Unable to convert the given Euler angles to a matrix using the given axis " - "codes [" + toString(angle3.second) + ", " + toString(angle2.second) + ", " + "codes [" + toString(angle3.second) + ", " + toString(angle2.second) + ", " + toString(angle1.second) + "]. Axis codes must be 1, 2, or 3."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1445,8 +1478,8 @@ namespace Isis { // Calculate the 3x3 rotation matrix generated by a rotation // of a specified angle about a specified axis. This rotation // is thought of as rotating the coordinate system. - // - //ROTATE(angle1, axis1); + // + //ROTATE(angle1, axis1); double sinAngle = sin(angle1.first.radians()); double cosAngle = cos(angle1.first.radians()); // get the index offset based on the axis number corresponding to this angle @@ -1460,7 +1493,7 @@ namespace Isis { m(index2, index1) = 0.0; m(index2, index2) = cosAngle; m(index2, index3) = sinAngle; m(index3, index1) = 0.0; m(index3, index2) = -sinAngle; m(index3, index3) = cosAngle; - // + // // C ROTMAT applies a rotation of ANGLE radians about axis IAXIS to a // C matrix. This rotation is thought of as rotating the coordinate // C system. @@ -1479,7 +1512,7 @@ namespace Isis { tempMatrix(index2, i) = cosAngle*m(index2, i) + sinAngle*m(index3, i); tempMatrix(index3, i) = -sinAngle*m(index2, i) + cosAngle*m(index3, i); } - + //CALL ROTMAT ( tempMatrix, ANGLE3, AXIS3, m ) sinAngle = sin(angle3.first.radians()); cosAngle = cos(angle3.first.radians()); @@ -1499,13 +1532,13 @@ namespace Isis { /** * Converts a rotation's representation from a list of Euler angles (3 angles, - * each with a corresponding axis) to a 3x3 matrix. - * + * each with a corresponding axis) to a 3x3 matrix. + * * @param eulerAngles The Euler angle representation of a rotation. - * + * * @return @b LinearAlgebra::Matrix The matrix representation of the rotation. - * - * @throw IException::Programmer "Unable to convert the given Euler angles to + * + * @throw IException::Programmer "Unable to convert the given Euler angles to * a matrix. Exactly 3 Euler angles are * required." */ @@ -1513,7 +1546,7 @@ namespace Isis { if (eulerAngles.size() != 3) { QString msg = "Unable to convert the given Euler angles to a matrix. " - "Exactly 3 Euler angles are required. The given list has [" + "Exactly 3 Euler angles are required. The given list has [" + toString((int) eulerAngles.size()) + "] angles."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1523,15 +1556,15 @@ namespace Isis { /** - * Converts a rotation's representation from a quaternion to a 3x3 matrix. - * Note, if the given vector is not a unit vector or the zero vector, this - * method will normalize it before computing the corresponding matrix. - * + * Converts a rotation's representation from a quaternion to a 3x3 matrix. + * Note, if the given vector is not a unit vector or the zero vector, this + * method will normalize it before computing the corresponding matrix. + * * @param quaternion A unit quaternion representation of a rotation. - * + * * @return @b LinearAlgebra::Matrix The matrix representation of the rotation. - * - * @throw IException::Programmer "Unable to convert the given vector to a + * + * @throw IException::Programmer "Unable to convert the given vector to a * rotation matrix. The given vector is not * a quaternion." */ @@ -1539,7 +1572,7 @@ namespace Isis { LinearAlgebra::Matrix LinearAlgebra::toMatrix(const Vector &quaternion) { if (quaternion.size() != 4) { QString msg = "Unable to convert the given vector to a rotation matrix. " - "The given vector with [" + toString((int) quaternion.size()) + "The given vector with [" + toString((int) quaternion.size()) + "] components is not a quaternion."; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1581,19 +1614,19 @@ namespace Isis { /** - * Sets the row of the given matrix to the values of the given vector. - * + * Sets the row of the given matrix to the values of the given vector. + * * @param matrix The address of the matrix to be altered. * @param vector The vector with the new values. * @param rowIndex The index of the row to be altered. - * - * @throw IException::Programmer "Unable to set the matrix row to the given + * + * @throw IException::Programmer "Unable to set the matrix row to the given * vector. Row index is out of bounds." */ void LinearAlgebra::setRow(Matrix &matrix, const Vector &vector, int rowIndex) { if ( (rowIndex+1 > (int) matrix.size1()) ) { - QString msg = "Unable to set the matrix row to the given vector. Row index " - + toString(rowIndex) + " is out of bounds. The given matrix only has " + QString msg = "Unable to set the matrix row to the given vector. Row index " + + toString(rowIndex) + " is out of bounds. The given matrix only has " + toString((int) matrix.size1()) + " rows." ; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1602,42 +1635,42 @@ namespace Isis { /** - * Sets the column of the given matrix to the values of the given vector. - * + * Sets the column of the given matrix to the values of the given vector. + * * @param matrix The address of the matrix to be altered. * @param vector The vector with the new values. * @param columnIndex The index of the column to be altered. - * - * @throw IException::Programmer "Unable to set the matrix column to the given + * + * @throw IException::Programmer "Unable to set the matrix column to the given * vector. Column index is out of bounds." */ void LinearAlgebra::setColumn(Matrix &matrix, const Vector &vector, int columnIndex) { if ( (columnIndex+1 > (int) matrix.size2()) ) { - QString msg = "Unable to set the matrix column to the given vector. Column index " - + toString(columnIndex) + " is out of bounds. The given matrix only has " + QString msg = "Unable to set the matrix column to the given vector. Column index " + + toString(columnIndex) + " is out of bounds. The given matrix only has " + toString((int) matrix.size1()) + " columns." ; throw IException(IException::Programmer, msg, _FILEINFO_); } - boost::numeric::ublas::column(matrix, columnIndex) = vector; + boost::numeric::ublas::column(matrix, columnIndex) = vector; } /** * Returns a vector whose components match those of the given matrix row. - * + * * @param matrix The matrix to pull values from. * @param rowIndex The index of the matrix row to grab. - * - * @return @b LinearAlgebra::Vector A vector whose values match the given row + * + * @return @b LinearAlgebra::Vector A vector whose values match the given row * of the given matrix. - * - * @throw IException::Programmer "Unable to get the matrix row to the given + * + * @throw IException::Programmer "Unable to get the matrix row to the given * vector. Row index is out of bounds." */ LinearAlgebra::Vector LinearAlgebra::row(const Matrix &matrix, int rowIndex) { if ( (rowIndex+1 > (int) matrix.size1()) ) { - QString msg = "Unable to get the matrix row to the given vector. Row index " - + toString(rowIndex) + " is out of bounds. The given matrix only has " + QString msg = "Unable to get the matrix row to the given vector. Row index " + + toString(rowIndex) + " is out of bounds. The given matrix only has " + toString((int) matrix.size1()) + " rows." ; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1647,20 +1680,20 @@ namespace Isis { /** * Returns a vector whose components match those of the given matrix column. - * + * * @param matrix The matrix to pull values from. * @param columnIndex The index of the matrix column to grab. - * - * @return @b LinearAlgebra::Vector A vector whose values match the given + * + * @return @b LinearAlgebra::Vector A vector whose values match the given * column of the given matrix. - * - * @throw IException::Programmer "Unable to get the matrix column to the given + * + * @throw IException::Programmer "Unable to get the matrix column to the given * vector. Column index is out of bounds." */ LinearAlgebra::Vector LinearAlgebra::column(const Matrix &matrix, int columnIndex) { if ( (columnIndex+1 > (int) matrix.size2()) ) { - QString msg = "Unable to get the matrix column to the given vector. Column index " - + toString(columnIndex) + " is out of bounds. The given matrix only has " + QString msg = "Unable to get the matrix column to the given vector. Column index " + + toString(columnIndex) + " is out of bounds. The given matrix only has " + toString((int) matrix.size1()) + " columns." ; throw IException(IException::Programmer, msg, _FILEINFO_); } @@ -1668,15 +1701,15 @@ namespace Isis { } - /** - * Constructs a 3 dimensional vector with the given component values. - * + /** + * Constructs a 3 dimensional vector with the given component values. + * * @param v0 The first component of the vector. - * @param v1 The second component of the vector. + * @param v1 The second component of the vector. * @param v2 The third component of the vector. - * + * * @return @b LinearAlgebra::Vector A vector containing the given values. - * + * */ LinearAlgebra::Vector LinearAlgebra::vector(double v0, double v1, double v2) { Vector v(3); @@ -1688,15 +1721,15 @@ namespace Isis { /** - * Constructs a 4 dimensional vector with the given component values. - * + * Constructs a 4 dimensional vector with the given component values. + * * @param v0 The first component of the vector. - * @param v1 The second component of the vector. + * @param v1 The second component of the vector. * @param v2 The third component of the vector. * @param v3 The fourth component of the vector. - * + * * @return @b LinearAlgebra::Vector A vector containing the given values. - * + * */ LinearAlgebra::Vector LinearAlgebra::vector(double v0, double v1, double v2, double v3) { Vector v(4); @@ -1709,11 +1742,11 @@ namespace Isis { /** - * Fills the first three elements of the given vector with the given values. - * + * Fills the first three elements of the given vector with the given values. + * * @param v A pointer to a 3-dimensional vector to be filled. * @param v0 The first component of the vector. - * @param v1 The second component of the vector. + * @param v1 The second component of the vector. * @param v2 The third component of the vector. */ void LinearAlgebra::setVec3(Vector *v, double v0, double v1, double v2) { @@ -1724,11 +1757,11 @@ namespace Isis { /** - * Fills the first four elements of the given vector with the given values. - * + * Fills the first four elements of the given vector with the given values. + * * @param v A pointer to a 4-dimensional vector to be filled. * @param v0 The first component of the vector. - * @param v1 The second component of the vector. + * @param v1 The second component of the vector. * @param v2 The third component of the vector. * @param v3 The fourth component of the vector. */ @@ -1740,17 +1773,17 @@ namespace Isis { } - /** - * Constructs a vector of given size using the given vector and starting - * index. - * + /** + * Constructs a vector of given size using the given vector and starting + * index. + * * @param v The original vector to get values from. * @param startIndex The index of the original vector, used to indicate * the first value that will be copied to the new vector. - * @param size The number of elements from the original vector to copy + * @param size The number of elements from the original vector to copy * to the new vector. - * - * @return @b LinearAlgebra::Vector A sub-vector containing values from + * + * @return @b LinearAlgebra::Vector A sub-vector containing values from * the original vector. */ LinearAlgebra::Vector LinearAlgebra::subVector(const Vector &v, int startIndex, int size) { @@ -1764,15 +1797,15 @@ namespace Isis { /** * A global function to format a LinearAlgebra::Vector as a QString and writes - * it to a QDebug stream. - * - * @see toString(LinearAlgebra::Vector) - * + * it to a QDebug stream. + * + * @see toString(LinearAlgebra::Vector) + * * @param dbg The stream where the vector will be written. * @param vector The vector to be written. - * + * * @return @b QDebug The stream with the QString-formatted vector. - * + * */ QDebug operator<<(QDebug dbg, const LinearAlgebra::Vector &vector) { QDebugStateSaver saver(dbg); @@ -1782,15 +1815,15 @@ namespace Isis { /** - * A global function to format a LinearAlgebra::Matrix as a QString and - * write it to a QDebug stream. There will be 4 spaces between each matrix - * entry and each row is written on a new line. - * + * A global function to format a LinearAlgebra::Matrix as a QString and + * write it to a QDebug stream. There will be 4 spaces between each matrix + * entry and each row is written on a new line. + * * @param dbg The stream where the vector will be written. * @param matrix The matrix to be written. - * + * * @return @b QDebug The stream with the QString-formatted matrix. - * + * */ QDebug operator<<(QDebug dbg, const LinearAlgebra::Matrix &matrix) { QDebugStateSaver saver(dbg); @@ -1806,15 +1839,15 @@ namespace Isis { /** - * A global function to format LinearAlgebra::Vector as a QString with the - * given precision. The string will be comma-separated entries encased by - * parentheses. - * + * A global function to format LinearAlgebra::Vector as a QString with the + * given precision. The string will be comma-separated entries encased by + * parentheses. + * * @param vector The vector to be converted. * @param precision Number of significant figures to convert. - * + * * @return @b QString The string-formatted vector. - * + * */ QString toString(const LinearAlgebra::Vector &vector, int precision) { QString result = "( "; diff --git a/isis/src/base/objs/LinearAlgebra/LinearAlgebra.h b/isis/src/base/objs/LinearAlgebra/LinearAlgebra.h index e46e21fab1..b05b31e474 100644 --- a/isis/src/base/objs/LinearAlgebra/LinearAlgebra.h +++ b/isis/src/base/objs/LinearAlgebra/LinearAlgebra.h @@ -55,6 +55,7 @@ namespace Isis { * prevent Boost from outputing debug information to standard out * when throwing exceptions. Fixes #2302. * @history 2017-12-12 Jeannie Backer - Added SymmetricMatrix typedef. + * @history 2021-02-17 Jesse Mapel - Added pseudoinverse method. * * * @todo document methods (a) add naif routine names to documentation where appropriate, @@ -147,6 +148,7 @@ namespace Isis { static Matrix identity(int size); static Matrix transpose(const Matrix &matrix); static Matrix inverse(const Matrix &matrix); + static Matrix pseudoinverse(const Matrix &matrix); static Matrix zeroMatrix(int rows, int columns); static Vector zeroVector(int size); diff --git a/isis/src/base/objs/Sensor/Sensor.cpp b/isis/src/base/objs/Sensor/Sensor.cpp index 8e43c8a425..be829f49d3 100644 --- a/isis/src/base/objs/Sensor/Sensor.cpp +++ b/isis/src/base/objs/Sensor/Sensor.cpp @@ -43,6 +43,7 @@ namespace Isis { * @param cube Cube whose label contains Instrument and Kernels groups. */ Sensor::Sensor(Cube &cube) : Spice(cube) { + m_newLookB = false; } @@ -351,7 +352,7 @@ namespace Isis { * Defaults to true. * * @return bool True if the look direction intersects the target. - * + * * @internal * @history 2017-03-23 Kris Becker - Added support for occlusion tests */ @@ -393,7 +394,7 @@ namespace Isis { * Defaults to true. * * @return bool True if the look direction intersects the target. - * + * * @internal * @history 2017-03-23 Kris Becker - Added support for occlusion test */ @@ -414,7 +415,7 @@ namespace Isis { Longitude lon(longitude, Angle::Degrees); Distance rad(radius, Distance::Meters); - shape->intersectSurface(SurfacePoint(lat, lon, rad), + shape->intersectSurface(SurfacePoint(lat, lon, rad), bodyRotation()->ReferenceVector(instrumentPosition()->Coordinate()), backCheck); @@ -434,10 +435,10 @@ namespace Isis { * Defaults to true. * * @return bool - * + * * @internal * @history 2017-03-23 Kris Becker - Added support for occlusion test - * + * */ bool Sensor::SetGround(const SurfacePoint &surfacePt, bool backCheck) { //std::cout << "Sensor::SetGround()\n"; @@ -449,7 +450,7 @@ namespace Isis { return false; } - shape->intersectSurface(surfacePt, + shape->intersectSurface(surfacePt, bodyRotation()->ReferenceVector(instrumentPosition()->Coordinate()), backCheck); @@ -523,7 +524,7 @@ namespace Isis { vector lookC = instrumentRotation()->ReferenceVector(lookDirectionJ2000()); v[0] = lookC[0]; v[1] = lookC[1]; - v[2] = lookC[2]; + v[2] = lookC[2]; } /** @@ -538,8 +539,8 @@ namespace Isis { lookB[2] = m_lookB[2]; return lookB; } - - + + /** * Returns the look direction in the camera coordinate system. * @@ -558,7 +559,9 @@ namespace Isis { * @return @b double The angle of right ascension, in degrees. */ double Sensor::RightAscension() { - if (m_newLookB) computeRaDec(); + if (m_newLookB) { + computeRaDec(); + } return m_ra; } @@ -569,7 +572,9 @@ namespace Isis { * @return @b double Declination angle, in degrees. */ double Sensor::Declination() { - if (m_newLookB) computeRaDec(); + if (m_newLookB) { + computeRaDec(); + } return m_dec; } diff --git a/isis/src/base/objs/Sensor/Sensor.h b/isis/src/base/objs/Sensor/Sensor.h index 3b54a584f1..22edbe937f 100644 --- a/isis/src/base/objs/Sensor/Sensor.h +++ b/isis/src/base/objs/Sensor/Sensor.h @@ -30,15 +30,15 @@ namespace Isis { /** * @brief Class for computing sensor ground coordinates * - * The sensor class allows for the computation of parameters related to orbiting instruments. - * In particular, a time and look direction can be set and from those the ground coordinate - * (latitude/longitude) along with phase, incidence, and emission angles can be computed. - * Likewise, a ground point can be set and look direction can be computed. This class is derived - * from the Spice class. + * The sensor class allows for the computation of parameters related to orbiting instruments. + * In particular, a time and look direction can be set and from those the ground coordinate + * (latitude/longitude) along with phase, incidence, and emission angles can be computed. + * Likewise, a ground point can be set and look direction can be computed. This class is derived + * from the Spice class. * - * An important capability of this class is the ability to use a surface model other than an - * ellipsoid when intersecting the look direction of the sensor with the planetary body. This - * allows for the generation of othrorectified products. The file containing the surface model + * An important capability of this class is the ability to use a surface model other than an + * ellipsoid when intersecting the look direction of the sensor with the planetary body. This + * allows for the generation of othrorectified products. The file containing the surface model * is a cube and is obtained from the labels in the follow form: * @code * Group = Kernels @@ -137,17 +137,17 @@ namespace Isis { * @history 2012-05-04 Steven Lambright - Re-enabled a safety check in the DemRadius() method * which was needed due to Projection not uniformly handling Null * inputs. Fixes #807. - * @history 2012-07-06 Debbie A. Cook - Updated Spice members to be more compliant with Isis + * @history 2012-07-06 Debbie A. Cook - Updated Spice members to be more compliant with Isis * coding standards. References #972. * @history 2012-09-06 Steven Lambright and Stuart Sides - Changed the constructors to take * Cube instead of Pvl to prevent redundant parsing. This * should eventually be refactored into a CubeLabel or similar object * so that an actual cube isn't required in the future, but for now * this enables the control net GUI to create a camera from a cube - * with no dimensions in the label. - * @history 2012-10-10 Debbie A. Cook - Moved the functionality related to the shape model into - * new classes: ShapeModel, EllipsoidShape, DemShape, and - * EquatorialCylindricalShape. Also modified to use new Target class. + * with no dimensions in the label. + * @history 2012-10-10 Debbie A. Cook - Moved the functionality related to the shape model into + * new classes: ShapeModel, EllipsoidShape, DemShape, and + * EquatorialCylindricalShape. Also modified to use new Target class. * References #775 and #1114 * @history 2012-10-25 Jeannie Backer - Changed resolution() method to lower * camel case. References #1181. @@ -163,6 +163,9 @@ namespace Isis { * SetLocalGround(bool backCheck) to make a callback to the * ShapeModel::isOccludedFrom() to test for point * visability. + * @history 2021-02-17 Kristin Berry, Jesse Mapel, and Stuart Sides - Made several functions + * virtual and moved look vector member variable to protected. Ensured + * that m_newLookB always initializes to the same value. */ class Sensor : public Spice { public: @@ -191,22 +194,22 @@ namespace Isis { Distance LocalRadius(Latitude lat, Longitude lon); Distance LocalRadius(double lat, double lon); - double PhaseAngle() const; - double EmissionAngle() const; - double IncidenceAngle() const; + virtual double PhaseAngle() const; + virtual double EmissionAngle() const; + virtual double IncidenceAngle() const; void LookDirection(double v[3]) const; std::vector lookDirectionJ2000() const; std::vector lookDirectionBodyFixed() const; - double RightAscension(); - double Declination(); + virtual double RightAscension(); + virtual double Declination(); // Return vector between spacecraft and surface point in body-fixed void SpacecraftSurfaceVector(double scSurfaceVector[3]) const; - double SlantDistance() const; + virtual double SlantDistance() const; double LocalSolarTime(); - double SolarDistance() const; + virtual double SolarDistance() const; double SpacecraftAltitude(); // Return local radius from dem @@ -232,15 +235,17 @@ namespace Isis { virtual QString spacecraftNameLong() const = 0; virtual QString spacecraftNameShort() const = 0; + protected: + SpiceDouble m_lookB[3]; //!< Look direction in body fixed + bool m_newLookB; //!< flag to indicate we need to recompute ra/dec + private: // This version of DemRadius is for SetLookDirection ONLY. Do not call. // DAC TODO Why is next declaration here? Don't move until I know // double DemRadius(double lat, double lon); void CommonInitialize(const std::string &demCube); - SpiceDouble m_lookB[3]; //!< Look direction in body fixed - bool m_newLookB; //!< flag to indicate we need to recompute ra/dec SpiceDouble m_ra; //!< Right ascension (sky longitude) SpiceDouble m_dec; //!< Decliation (sky latitude) void computeRaDec(); //!< Computes the ra/dec from the look direction diff --git a/isis/src/base/objs/Spice/Spice.cpp b/isis/src/base/objs/Spice/Spice.cpp index 3c881fe95b..b4cbf44b23 100644 --- a/isis/src/base/objs/Spice/Spice.cpp +++ b/isis/src/base/objs/Spice/Spice.cpp @@ -63,19 +63,15 @@ namespace Isis { // TODO: DOCUMENT EVERYTHING Spice::Spice(Cube &cube) { Pvl &lab = *cube.label(); - PvlGroup kernels = lab.findGroup("Kernels", Pvl::Traverse); - bool hasTables = (kernels["TargetPosition"][0] == "Table"); - init(lab, !hasTables); - } - - /** - * Constructs a Spice object. - * - * @param lab Pvl labels. - * @param noTables Indicates the use of tables. - */ - Spice::Spice(Cube &cube, bool noTables) { - init(*cube.label(), noTables); + if (cube.hasBlob("String", "CSMState")) { + csmInit(cube, lab); + } + else { + PvlGroup kernels = lab.findGroup("Kernels", Pvl::Traverse); + bool hasTables = (kernels["TargetPosition"][0] == "Table"); + // BONUS TODO: update to pull out separate init methods + init(lab, !hasTables); + } } @@ -89,25 +85,29 @@ namespace Isis { init(lab, true, isd); } + /** - * Initialization of Spice object. + * Initialize the Spice object for a CSMCamera. + * This sets up the Spice/Sensor/Camera object to not have any SPICE dependent + * members initialized. * - * @param lab Pvl labels - * @param noTables Indicates the use of tables. - * - * @throw Isis::IException::Io - "Can not find NAIF code for NAIF target" - * @throw Isis::IException::Camera - "No camera pointing available" - * @throw Isis::IException::Camera - "No instrument position available" - * - * @internal - * @history 2011-02-08 Jeannie Walldren - Initialize pointers to null. + * @param cube The Cube containing image data for the camera + * @param label The label containing information for the camera */ - void Spice::init(Pvl &lab, bool noTables, json isd) { + void Spice::csmInit(Cube &cube, Pvl label) { + defaultInit(); + m_target = new Target; NaifStatus::CheckErrors(); - // Initialize members + } + + + /** + * Default initialize the members of the SPICE object. + */ + void Spice::defaultInit() { m_solarLongitude = new Longitude; - m_et = NULL; + m_et = nullptr; m_kernels = new QVector; m_startTime = new iTime; @@ -120,13 +120,42 @@ namespace Isis { m_endTimePadding = new SpiceDouble; *m_endTimePadding = 0; - m_instrumentPosition = NULL; - m_instrumentRotation = NULL; - m_sunPosition = NULL; - m_bodyRotation = NULL; + m_instrumentPosition = nullptr; + m_instrumentRotation = nullptr; + + m_sunPosition = nullptr; + m_bodyRotation = nullptr; m_allowDownsizing = false; + m_spkCode = nullptr; + m_ckCode = nullptr; + m_ikCode = nullptr; + m_sclkCode = nullptr; + m_spkBodyCode = nullptr; + m_bodyFrameCode = nullptr; + m_target = nullptr; + } + + + /** + * Initialization of Spice object. + * + * @param lab Pvl labels + * @param noTables Indicates the use of tables. + * + * @throw Isis::IException::Io - "Can not find NAIF code for NAIF target" + * @throw Isis::IException::Camera - "No camera pointing available" + * @throw Isis::IException::Camera - "No instrument position available" + * + * @internal + * @history 2011-02-08 Jeannie Walldren - Initialize pointers to null. + */ + void Spice::init(Pvl &lab, bool noTables, json isd) { + NaifStatus::CheckErrors(); + // Initialize members + defaultInit(); + m_spkCode = new SpiceInt; m_ckCode = new SpiceInt; m_ikCode = new SpiceInt; @@ -159,6 +188,7 @@ namespace Isis { m_usingNaif = !lab.hasObject("NaifKeywords") || noTables; m_usingAle = false; + // Modified to load planetary ephemeris SPKs before s/c SPKs since some // missions (e.g., MESSENGER) may augment the s/c SPK with new planet // ephemerides. (2008-02-27 (KJB)) @@ -193,8 +223,7 @@ namespace Isis { m_usingAle = true; } catch(...) { - - // Backup to stadnard ISIS implementation + // Backup to standard ISIS implementation if (noTables) { load(kernels["TargetPosition"], noTables); load(kernels["InstrumentPosition"], noTables); @@ -250,11 +279,10 @@ namespace Isis { // NAIF keywords have been pulled from the cube labels, so we can find target body codes // that are defined in kernels and not just body codes build into spicelib // TODO: Move this below the else once the rings code above has been refactored - m_target = new Target(this, lab); + m_target = new Target(this, lab); } - // Get NAIF ik, spk, sclk, and ck codes // // Use ikcode to get parameters from instrument kernel such as focal @@ -292,6 +320,7 @@ namespace Isis { // m_target doesn't have the getDouble method so Spice gets the radii for it m_target->setRadii(radii); } + *m_spkBodyCode = m_target->naifBodyCode(); // Override them if they exist in the labels @@ -442,11 +471,10 @@ namespace Isis { Table t("InstrumentPosition", lab.fileName(), lab); m_instrumentPosition->LoadCache(t); } - - NaifStatus::CheckErrors(); } + /** * Loads/furnishes NAIF kernel(s) * @@ -584,7 +612,6 @@ namespace Isis { delete m_kernels; m_kernels = NULL; } - NaifStatus::CheckErrors(); } @@ -972,6 +999,7 @@ namespace Isis { return *m_naifKeywords; } + /** * Virtual method that returns the pixel resolution of the sensor in * meters/pix. @@ -979,7 +1007,7 @@ namespace Isis { * @return @b double Resolution value of 1.0 */ double Spice::resolution() { - return 1.; + return 1.0; }; @@ -1286,6 +1314,7 @@ namespace Isis { NaifStatus::CheckErrors(); } + /** * Returns the sub-solar latitude/longitude in universal coordinates (0-360 * positive east, ocentric) diff --git a/isis/src/base/objs/Spice/Spice.h b/isis/src/base/objs/Spice/Spice.h index 76e4b88a03..2ba6c0d694 100644 --- a/isis/src/base/objs/Spice/Spice.h +++ b/isis/src/base/objs/Spice/Spice.h @@ -73,7 +73,7 @@ namespace Isis { * labels * @history 2003-10-28 Jeff Anderson - Changed SpaceCraft to Spacecraft in * labels and method names - * @history 2003-11-03 Jeff Anderson - Added SubSolarPoint and + * @history 2003-11-03 Jeff Anderson - Added SubSolarPoint and * SubSpacecraftPoint methods * @history 2003-11-12 Jeff Anderson - Added Target method * @history 2004-01-14 Jeff Anderson - Changed how the SPK, CK, and @@ -225,7 +225,7 @@ namespace Isis { * implementations to cpp file. Changed Resolution() * method to lower camel case. Added documentation. * Fixes #1181. - * @history 2012-10-31 Kris Becker - Added implementation for swapping of + * @history 2012-10-31 Kris Becker - Added implementation for swapping of * observer/target and light time correction to * surface. Fixes (mostly) #0909, #1136 and #1223. * @history 2012-12-10 Kris Becker - A newly designed class, @@ -261,72 +261,74 @@ namespace Isis { * build into spicelib. References #3934 * @history 2016-10-19 Kristin Berry - Added exception to Spice::time() to throw if m_et is * NULL. Also added isTimeSet(), a function that will return true if - * m_et is set. References #4476. + * m_et is set. References #4476. * @history 2016-10-21 Jeannie Backer - Reorder method signatures and member variable * declarations to fit ISIS coding standards. References #4476. * @history 2018-06-07 Debbie A Cook - Added BODY_CODE to Naif keywords. This code * is used in the target body radii keyword name. Isis retrieves this code - * from the standard PCK. Because target bodies new to Naif are not + * from the standard PCK. Because target bodies new to Naif are not * included in the standard PCK, missions create a special body-specific * PCK to define the new body, including its body code. This PCK is only - * loaded in spiceinit so the code needs to be saved so that the radii + * loaded in spiceinit so the code needs to be saved so that the radii * keyword can be created to retrieve the target radii. * @history 2019-04-16 Kristin Berry - Added a parameter to getClockTime called clockTicks which * defaults to false. When set to true, this indicates that the input value * is in encoded clock ticks, rather than a full spacecraft clock time * string. As such, when used sct2e_c is used to convert to an ET rather - * than scs2e_c. + * than scs2e_c. + * @history 2021-02-17 Kristin Berry, Jesse Mapel, and Stuart Sides - Made several methods virtual, + * moved several member variables to protected, and added initialization + * path for a sensor model without SPICE data. */ class Spice { public: // constructors Spice(Cube &cube); - Spice(Cube &cube, bool noTables); Spice(Pvl &lab, nlohmann::json); // destructor virtual ~Spice(); // Methods - void setTime(const iTime &time); + virtual void setTime(const iTime &time); void instrumentPosition(double p[3]) const; - void instrumentBodyFixedPosition(double p[3]) const; - void sunPosition(double p[3]) const; - double targetCenterDistance() const; - double sunToBodyDist() const; - - Longitude solarLongitude(); - void instrumentBodyFixedVelocity(double v[3]) const; + virtual void instrumentBodyFixedPosition(double p[3]) const; + virtual void sunPosition(double p[3]) const; + virtual double targetCenterDistance() const; + virtual double sunToBodyDist() const; + + virtual Longitude solarLongitude(); + virtual void instrumentBodyFixedVelocity(double v[3]) const; iTime time() const; void radii(Distance r[3]) const; - void createCache(iTime startTime, iTime endTime, - const int size, double tol); - iTime cacheStartTime() const; - iTime cacheEndTime() const; + virtual void createCache(iTime startTime, iTime endTime, + const int size, double tol); + virtual iTime cacheStartTime() const; + virtual iTime cacheEndTime() const; - void subSpacecraftPoint(double &lat, double &lon); - void subSolarPoint(double &lat, double &lon); + virtual void subSpacecraftPoint(double &lat, double &lon); + virtual void subSolarPoint(double &lat, double &lon); - Target *target() const; + virtual Target *target() const; QString targetName() const; - iTime getClockTime(QString clockValue, - int sclkCode = -1, - bool clockTicks=false); + virtual iTime getClockTime(QString clockValue, + int sclkCode = -1, + bool clockTicks=false); SpiceDouble getDouble(const QString &key, int index = 0); SpiceInt getInteger(const QString &key, int index = 0); QString getString(const QString &key, int index = 0); - SpicePosition *sunPosition() const; - SpicePosition *instrumentPosition() const; - SpiceRotation *bodyRotation() const; - SpiceRotation *instrumentRotation() const; - + virtual SpicePosition *sunPosition() const; + virtual SpicePosition *instrumentPosition() const; + virtual SpiceRotation *bodyRotation() const; + virtual SpiceRotation *instrumentRotation() const; + bool isUsingAle(); bool hasKernels(Pvl &lab); - bool isTimeSet(); + bool isTimeSet(); SpiceInt naifBodyCode() const; SpiceInt naifSpkCode() const; @@ -358,6 +360,7 @@ namespace Isis { void storeValue(QString key, int index, SpiceValueType type, QVariant value); QVariant readStoredValue(QString key, SpiceValueType type, int index); + virtual void computeSolarLongitude(iTime et); // Leave these protected so that inheriting classes don't // have to convert between double and spicedouble @@ -375,22 +378,22 @@ namespace Isis { space so that conversions between double and SpiceDouble do not have to occur in inheriting classes.*/ - + Target *m_target; //!< Target of the observation + iTime *m_et; //!< Ephemeris time (read NAIF documentation for a detailed description) + Longitude *m_solarLongitude; //!< Body rotation solar longitude value private: // Don't allow copies Spice(const Spice &other); Spice &operator=(const Spice &other); - + void init(Pvl &pvl, bool noTables, nlohmann::json isd = NULL); + void csmInit(Cube &cube, Pvl label); + void defaultInit(); void load(PvlKeyword &key, bool notab); - void computeSolarLongitude(iTime et); - Longitude *m_solarLongitude; //!< Body rotation solar longitude value - iTime *m_et; //!< Ephemeris time (read NAIF documentation for a detailed description) QVector * m_kernels; //!< Vector containing kernels filenames - Target *m_target; //!< Target of the observation // cache stuff iTime *m_startTime; //!< Corrected start (shutter open) time of the observation. @@ -417,17 +420,18 @@ namespace Isis { SpiceInt *m_ikCode; //!< Instrument kernel (IK) code SpiceInt *m_sclkCode; //!< Spacecraft clock correlation kernel (SCLK) code SpiceInt *m_spkBodyCode; //!< Spacecraft and planet ephemeris kernel (SPK) body code - SpiceInt *m_bodyFrameCode; /**< Naif's BODY_FRAME_CODE value. It is read - from the labels, if it exists. Otherwise, + SpiceInt *m_bodyFrameCode; /**< Naif's BODY_FRAME_CODE value. It is read + from the labels, if it exists. Otherwise, it's calculated by the init() method.*/ PvlObject *m_naifKeywords; //!< NaifKeywords PvlObject from cube - bool m_usingNaif; /**< Indicates whether we are reading values from the + bool m_usingNaif; /**< Indicates whether we are reading values from the NaifKeywords PvlObject in cube*/ - bool m_usingAle; /**< Indicate whether we are reading values from an ISD returned + bool m_usingAle; /**< Indicate whether we are reading values from an ISD returned from ALE */ + }; } diff --git a/isis/src/base/objs/StringBlob/StringBlob.cpp b/isis/src/base/objs/StringBlob/StringBlob.cpp index 9c1a3d3b82..fb754ca905 100644 --- a/isis/src/base/objs/StringBlob/StringBlob.cpp +++ b/isis/src/base/objs/StringBlob/StringBlob.cpp @@ -46,16 +46,15 @@ namespace Isis { * Prepare to write string to output cube */ void StringBlob::WriteInit() { - int bytes = m_string.size(); + p_nbytes = m_string.size(); - char *temp = p_buffer; - p_buffer = new char[p_nbytes+bytes]; - if (temp != NULL) memcpy(p_buffer, temp, p_nbytes); - const char *ptr = m_string.c_str(); - memcpy(&p_buffer[p_nbytes], (void *)ptr, bytes); - p_nbytes += bytes; + if (p_buffer != NULL) { + delete p_buffer; + } - if (temp != NULL) delete [] temp; + p_buffer = new char[p_nbytes]; + const char *ptr = m_string.c_str(); + memcpy(p_buffer, (void *)ptr, p_nbytes); } /** diff --git a/isis/src/base/objs/SurfacePoint/SurfacePoint.h b/isis/src/base/objs/SurfacePoint/SurfacePoint.h index a7c53263d0..1f647f81bb 100644 --- a/isis/src/base/objs/SurfacePoint/SurfacePoint.h +++ b/isis/src/base/objs/SurfacePoint/SurfacePoint.h @@ -137,7 +137,7 @@ namespace Isis { * Defines the coordinate typ, units, and coordinate index for some of the output methods */ enum CoordinateType { - Latitudinal, /**< Planetocentric latitudinal (lat/lon/rad) coordinates */ + Latitudinal, /**< Planetocentric latitudinal (lat/lon/rad) coordinates */ Rectangular /**< Body-fixed rectangular x/y/z coordinates */ }; enum CoordUnits { diff --git a/isis/src/base/objs/Target/Target.cpp b/isis/src/base/objs/Target/Target.cpp index 9e6b38a7fc..ab8eb2d0c3 100644 --- a/isis/src/base/objs/Target/Target.cpp +++ b/isis/src/base/objs/Target/Target.cpp @@ -43,7 +43,7 @@ namespace Isis { m_systemCode = new SpiceInt; m_radii.resize(3, Distance()); - m_spice = spice; + m_spice = spice; // If we get this far, we know we have a kernels group. Spice requires it. PvlGroup &kernels = lab.findGroup("Kernels", Pvl::Traverse); @@ -82,6 +82,7 @@ namespace Isis { SpiceChar naifBuf[40]; SpiceBoolean found; bodc2n_c((SpiceInt) *m_systemCode, sizeof(naifBuf), naifBuf, &found); + NaifStatus::CheckErrors(); string s(naifBuf); (*m_systemName).append(s.c_str()); @@ -98,6 +99,30 @@ namespace Isis { } + /** + * Construct a Target without SPICE data. + * The label should contain an Instrument group with a TargetName and + * a Kernels group with a ShapeModel. + * + * @param label Label containing information about the Target. + */ + Target::Target(Pvl &label) { + // Initialize everything to null + m_bodyCode = NULL; + m_systemCode = NULL; + m_name = NULL; + m_systemName = NULL; + m_spice = NULL; + init(); + + PvlGroup &inst = label.findGroup("Instrument", Pvl::Traverse); + QString targetName = inst["TargetName"][0]; + setName(targetName); + + m_shape = ShapeModelFactory::create(this, label); + } + + /** * Constructs an empty Target object * @@ -187,23 +212,23 @@ namespace Isis { return code; } // getInteger automatically calls Spice::readValue which looks in the NaifKeywords - else if (lab.hasObject("NaifKeywords") + else if (lab.hasObject("NaifKeywords") && lab.findObject("NaifKeywords").hasKeyword("BODY_CODE") ) { code = int(lab.findObject("NaifKeywords").findKeyword("BODY_CODE")); return code; } else { - throw IException(e, - IException::Unknown, - "BODY_CODE not found for this Target.", + throw IException(e, + IException::Unknown, + "BODY_CODE not found for this Target.", _FILEINFO_); } } catch (IException &e2) { e.append(e2); - throw IException(e, - IException::Unknown, - "Unable to look up NAIF body code for this Target.", + throw IException(e, + IException::Unknown, + "Unable to look up NAIF body code for this Target.", _FILEINFO_); } } @@ -234,18 +259,18 @@ namespace Isis { /** - * This method returns a Mapping group containing TargetName, - * EquatorialRadius, and PolarRadius in addition to all of the keywords + * This method returns a Mapping group containing TargetName, + * EquatorialRadius, and PolarRadius in addition to all of the keywords * that are in the given mapGroup. * * @param cubeLab Pvl labels for the image. - * @param mapGroup A const reference to a PvlGroup that contains + * @param mapGroup A const reference to a PvlGroup that contains * mapping parameters for the projection. * - * @return PvlGroup The Mapping Group for the projection including the + * @return PvlGroup The Mapping Group for the projection including the * keywords TargetName, EquatorialRadius, and * PolarRadius. - * + * */ PvlGroup Target::radiiGroup(Pvl &cubeLab, const PvlGroup &mapGroup) { PvlGroup mapping = mapGroup; @@ -253,11 +278,11 @@ namespace Isis { // Check to see if the mapGroup already has the target radii. // If BOTH radii are already in the mapGroup then just return the given // mapping group as is. - if (mapping.hasKeyword("EquatorialRadius") + if (mapping.hasKeyword("EquatorialRadius") && mapping.hasKeyword("PolarRadius")) { return mapping; } - + // If radii values are not in the given mapping group, we will get the target from the mapping // group or cube label and attempt to use NAIF routines to find the radii. QString target = ""; @@ -280,7 +305,7 @@ namespace Isis { // target name still not found, throw error if (target.isEmpty()) { - throw IException(IException::Unknown, + throw IException(IException::Unknown, "Unable to find a TargetName keyword in the given PVL.", _FILEINFO_); } @@ -290,7 +315,7 @@ namespace Isis { // radii in the NaifKeywords object of the labels PvlGroup radii = Target::radiiGroup(target); - // Successfully found radii using target name. + // Successfully found radii using target name. // Copy the EquatorialRadius and PolorRadius and we are done. mapping.addKeyword( radii.findKeyword("EquatorialRadius"), PvlContainer::Replace ); mapping.addKeyword( radii.findKeyword("PolarRadius"), PvlContainer::Replace ); @@ -299,17 +324,17 @@ namespace Isis { catch (IException &e) { // If all previous attempts fail, look for the radii using the body frame // code in the NaifKeywords object. - // Note: We will only look in the given label for the values after SPICELIB + // Note: We will only look in the given label for the values after SPICELIB // routines have failed, to preserve backwards compatibility (since this // label check is new). if (cubeLab.hasObject("NaifKeywords")) { PvlObject naifKeywords = cubeLab.findObject("NaifKeywords"); - + // Try using the target bodycode_RADII keyword in the NaifKeywords PVL object - + try { - + SpiceInt bodyCode = 0; try { // Try using the target bodycode_RADII keyword in the NaifKeywords PVL object @@ -345,7 +370,7 @@ namespace Isis { "meters"), PvlContainer::Replace); mapping.addKeyword( PvlKeyword("PolarRadius", - toString(toDouble(radii[2]) * 1000.0), + toString(toDouble(radii[2]) * 1000.0), "meters"), PvlContainer::Replace); return mapping; @@ -363,16 +388,16 @@ namespace Isis { /** - * Creates a Pvl Group with keywords TargetName, EquitorialRadius, and - * PolarRadius. The values for the radii will be retrieved from the most - * recent Target Attitude and Shape Naif kernel available in the Isis data - * area. + * Creates a Pvl Group with keywords TargetName, EquitorialRadius, and + * PolarRadius. The values for the radii will be retrieved from the most + * recent Target Attitude and Shape Naif kernel available in the Isis data + * area. * * @param target The name of the body for which the radii will be retrieved. * * @throw IException::Io - "Could not convert target name to NAIF code." - * - * @return PvlGroup Group named "Mapping" with keywords TargetName, + * + * @return PvlGroup Group named "Mapping" with keywords TargetName, * EquatorialRadius, and PolarRadius. */ PvlGroup Target::radiiGroup(QString target) { @@ -396,7 +421,7 @@ namespace Isis { bodyCode = lookupNaifBodyCode(target); } catch (IException &e) { - QString msg = "Unable to find target radii for given target [" + QString msg = "Unable to find target radii for given target [" + target + "]."; throw IException(IException::Io, msg, _FILEINFO_); } @@ -413,15 +438,15 @@ namespace Isis { /** - * Convenience method called by the public radii() methods to - * compute the target radii using a body code recognized by NAIF. - * - * The PVL group contains only the EquatorialRadius and PolarRadius - * keywords. This group does not contain the Target keyword. - * - * @param bodyFrameCode A recognized NAIF code that represents the target body. - * - * @return PvlGroup containing EquatorialRadius and PolarRadius keywords. + * Convenience method called by the public radii() methods to + * compute the target radii using a body code recognized by NAIF. + * + * The PVL group contains only the EquatorialRadius and PolarRadius + * keywords. This group does not contain the Target keyword. + * + * @param bodyFrameCode A recognized NAIF code that represents the target body. + * + * @return PvlGroup containing EquatorialRadius and PolarRadius keywords. */ PvlGroup Target::radiiGroup(int bodyCode) { @@ -438,19 +463,19 @@ namespace Isis { furnsh_c(kernName.toLatin1().data()); pckLoaded = true; } - + // Get the radii from NAIF SpiceInt n; SpiceDouble radii[3]; bodvar_c(bodyCode, "RADII", &n, radii); - + try { NaifStatus::CheckErrors(); } catch (IException &e) { QString msg = "Unable to find radii for target code [" + toString(bodyCode) + "]. Target code was not found in furnished kernels."; - + throw IException(e, IException::Unknown, msg, _FILEINFO_); } @@ -588,6 +613,9 @@ namespace Isis { * @param r[] Radii of the target in kilometers */ void Target::setRadii(std::vector radii) { + if (m_radii.size() < 3) { + m_radii.resize(3, Distance()); + } m_radii[0] = radii[0]; m_radii[1] = radii[1]; m_radii[2] = radii[2]; @@ -595,7 +623,34 @@ namespace Isis { /** - * Return the shape + * Set the name for the Target. + * This function should be used if the target name is not available on the label + * originally used to initialize the Target. + * + * @param name The new name of the Target + */ + void Target::setName(QString name) { + if (m_name == NULL) { + m_name = new QString; + } + *m_name = name; + } + + + /** + * Set the Spice pointer for the Target. + * This function should be used if the Target was initialized without SPICE + * data but is still needed by a sensor model. + * + * @param spice A pointer to the new Spice object + */ + void Target::setSpice(Spice *spice) { + m_spice = spice; + } + + + /** + * Return the shape */ ShapeModel *Target::shape() const { return m_shape; @@ -603,7 +658,7 @@ namespace Isis { /** - * Return the spice object + * Return the spice object */ Spice *Target::spice() const { return m_spice; diff --git a/isis/src/base/objs/Target/Target.h b/isis/src/base/objs/Target/Target.h index c005b1ea00..b43ad6a5f9 100644 --- a/isis/src/base/objs/Target/Target.h +++ b/isis/src/base/objs/Target/Target.h @@ -56,12 +56,16 @@ namespace Isis { * These are not the same. Naif tags the body radii keyword with the * Naif body code. The Naif body frame code refers to the orientation * (SpiceRotation) of the body. References #4649 and #501. + * @history 2021-02-17 Kristin Berry, Jesse Mapel, and Stuart Sides - Added the ability to + * create a Target without SPICE data and later set the sensor + * model pointer. */ class Target { public: // constructors Target(Spice *spice, Pvl &label); + Target(Pvl &label); Target(); //! Destroys the Target @@ -77,6 +81,9 @@ namespace Isis { void restoreShape(); void setShapeEllipsoid(); void setRadii(std::vector radii); + void setName(QString name); + void setSpice(Spice *spice); + ShapeModel *shape() const; Spice *spice() const; diff --git a/isis/src/base/objs/iTime/iTime.cpp b/isis/src/base/objs/iTime/iTime.cpp index edc808cffa..ac3b593964 100644 --- a/isis/src/base/objs/iTime/iTime.cpp +++ b/isis/src/base/objs/iTime/iTime.cpp @@ -8,6 +8,8 @@ find files of those names at the top level of this repository. **/ #include #include +#include + #include "Preference.h" #include "FileName.h" @@ -32,8 +34,8 @@ namespace Isis { } /** - * Constructs a iTime object and initializes it to the time from the argument. - * + * Constructs a iTime object and initializes it to the time from the argument. + * * @param time A time string formatted in standard UTC or similar format. * Example:"2000/12/31 23:59:01.6789" or "2000-12-31T23:59:01.6789" */ @@ -416,7 +418,7 @@ namespace Isis { if(Second() < 10) utc += "0" + SecondString(precision); else utc += SecondString(precision); - + return utc; } @@ -428,6 +430,29 @@ namespace Isis { } void iTime::setUtc(QString utcString) { + // If the time string is in ISO basic format add separators for utc2et + if ( utcString.contains("T") && // Check for ISO T format + !utcString.contains("-") && // Check for missing data separator + !utcString.contains(":")) { // Check for missing time separator + QString dateString = utcString.split("T").front(); + dateString.insert(4, "-"); + // If format is YYYYDOY we are done with the date string + // Otherwise we are in YYYYMMDD format + if (dateString.size() > 8) { + dateString.insert(7, "-"); + } + + QString timeString = utcString.split("T").back(); + // If the format is hh or hhmm, resize and pad with 0s out to hh0000 or hhmm00 + if (timeString.size() < 6) { + timeString.resize(6, '0'); + } + timeString.insert(2, ":"); + timeString.insert(5, ":"); + + utcString = dateString + "T" + timeString; + } + NaifStatus::CheckErrors(); LoadLeapSecondKernel(); diff --git a/isis/src/base/objs/iTime/iTime.h b/isis/src/base/objs/iTime/iTime.h index 2ad44fc96a..46511c71f3 100644 --- a/isis/src/base/objs/iTime/iTime.h +++ b/isis/src/base/objs/iTime/iTime.h @@ -59,6 +59,8 @@ namespace Isis { * Fixes #5325. * @history 2019-06-15 Kristin Berry - Added a precision option to UTC to specify the precision * of the output value. The default (old) precision is used if no argument is specified. + * @history 2021-02-17 Jesse Mapel - Added the ability to pass ISO 8601 basic format time + * strings to setUtc. */ class iTime { public: diff --git a/isis/src/base/objs/iTime/iTime.truth b/isis/src/base/objs/iTime/iTime.truth deleted file mode 100644 index f17537320c..0000000000 --- a/isis/src/base/objs/iTime/iTime.truth +++ /dev/null @@ -1,100 +0,0 @@ -Unit test for iTime - Test of date = 2003/01/02 12:15:01.1234 - Year = 2003 - Year = 2003 - Month = 1 - Month = 1 - Day = 2 - Day = 2 - Hour = 12 - Hour = 12 - Minute = 15 - Minute = 15 - Second = 1.1234 - Second = 1.1234 - Day of Year = 2 - Day of Year = 2 - Et = 94781765.307363 - Et = 94781765.3 - UTC = 2003-01-02T12:15:01.1234 - - Test of date = 2000-12-31T23:59:01.6789 - Year = 2000 - Year = 2000 - Month = 12 - Month = 12 - Day = 31 - Day = 31 - Hour = 23 - Hour = 23 - Minute = 59 - Minute = 59 - Second = 1.6789 - Second = 1.6789 - Day of Year = 366 - Day of Year = 366 - Et = 31579205.862834 - Et = 31579205.9 - UTC = 2000-12-31T23:59:01.6789 - - Test of date = 31579205.862834 - Year = 2000 - Year = 2000 - Month = 12 - Month = 12 - Day = 31 - Day = 31 - Hour = 23 - Hour = 23 - Minute = 59 - Minute = 59 - Second = 1.6789 - Second = 1.6789 - Day of Year = 366 - Day of Year = 366 - Et = 31579205.862834 - Et = 31579205.9 - UTC = 2000-12-31T23:59:01.6789 - - Test of iTime operator>= member - 94781765.307363 >= 94781765.307363 = 1 - 94781765.307363 >= 94781765.307413 = 0 - 94781765.307363 >= 94781765.306963 = 1 - - Test of iTime operator<= member - 94781765.307363 <= 94781765.307363 = 1 - 94781765.307363 <= 94781765.307413 = 1 - 94781765.307363 <= 94781765.306963 = 0 - - Test of iTime operator> member - 94781765.307363 > 94781765.307363 = 0 - 94781765.307363 > 94781765.307413 = 0 - 94781765.307363 > 94781765.306963 = 1 - - Test of iTime operator< member - 94781765.307363 < 94781765.307363 = 0 - 94781765.307363 < 94781765.307413 = 1 - 94781765.307363 < 94781765.306963 = 0 - - Test of iTime operator!= member - 94781765.307363 != 94781765.307363 = 0 - 94781765.307363 != 94781765.307413 = 1 - - Test of iTime operator== member - 94781765.307363 == 94781765.307363 = 1 - 94781765.307363 == 94781765.307413 = 0 - - Test of iTime operator+(double) member - 1.01 - - Test of iTime operator+=(double) member - 1.01 - - Test of iTime operator-(double) member - -1.01 - - Test of iTime operator-=(double) member - -1.01 - - Test of double operator-(iTIme) member - -100.001 diff --git a/isis/src/base/objs/iTime/unitTest.cpp b/isis/src/base/objs/iTime/unitTest.cpp deleted file mode 100644 index f621fb82a8..0000000000 --- a/isis/src/base/objs/iTime/unitTest.cpp +++ /dev/null @@ -1,296 +0,0 @@ -/** This is free and unencumbered software released into the public domain. -The authors of ISIS do not claim copyright on the contents of this file. -For more details about the LICENSE terms and the AUTHORS, you will -find files of those names at the top level of this repository. **/ - -/* SPDX-License-Identifier: CC0-1.0 */ -#include -#include -#include "IException.h" -#include "iTime.h" - -#include "Preference.h" - -using namespace Isis; -using namespace std; - -int main(int argc, char *argv[]) { - - Preference::Preferences(true); - - cout << "Unit test for iTime" << endl; - - try { - cout << setprecision(9); - QString test = "2003/01/02 12:15:01.1234"; - iTime *time = new iTime(test); - cout << " Test of date = " << test << endl; - cout << " Year = " << time->YearString() << endl; - cout << " Year = " << time->Year() << endl; - cout << " Month = " << time->MonthString() << endl; - cout << " Month = " << time->Month() << endl; - cout << " Day = " << time->DayString() << endl; - cout << " Day = " << time->Day() << endl; - cout << " Hour = " << time->HourString() << endl; - cout << " Hour = " << time->Hour() << endl; - cout << " Minute = " << time->MinuteString() << endl; - cout << " Minute = " << time->Minute() << endl; - cout << " Second = " << time->SecondString() << endl; - cout << " Second = " << time->Second() << endl; - cout << " Day of Year = " << time->DayOfYearString() << endl; - cout << " Day of Year = " << time->DayOfYear() << endl; - cout << " Et = " << time->EtString() << endl; - cout << " Et = " << time->Et() << endl; - cout << " UTC = " << time->UTC() << endl; - } - catch(IException &error) { - error.print(); - } - - - double saveEt = 0.0; - try { - cout << endl; - cout << setprecision(9); - QString test = "2000-12-31T23:59:01.6789"; - iTime time; - time = test; - cout << " Test of date = " << test << endl; - cout << " Year = " << time.YearString() << endl; - cout << " Year = " << time.Year() << endl; - cout << " Month = " << time.MonthString() << endl; - cout << " Month = " << time.Month() << endl; - cout << " Day = " << time.DayString() << endl; - cout << " Day = " << time.Day() << endl; - cout << " Hour = " << time.HourString() << endl; - cout << " Hour = " << time.Hour() << endl; - cout << " Minute = " << time.MinuteString() << endl; - cout << " Minute = " << time.Minute() << endl; - cout << " Second = " << time.SecondString() << endl; - cout << " Second = " << time.Second() << endl; - cout << " Day of Year = " << time.DayOfYearString() << endl; - cout << " Day of Year = " << time.DayOfYear() << endl; - cout << " Et = " << time.EtString() << endl; - cout << " Et = " << time.Et() << endl; - cout << " UTC = " << time.UTC() << endl; - saveEt = time.Et(); - } - catch(IException &error) { - error.print(); - } - - - try { - cout << endl; - cout << setprecision(9); - iTime time(saveEt); - cout << " Test of date = " << time.EtString() << endl; - cout << " Year = " << time.YearString() << endl; - cout << " Year = " << time.Year() << endl; - cout << " Month = " << time.MonthString() << endl; - cout << " Month = " << time.Month() << endl; - cout << " Day = " << time.DayString() << endl; - cout << " Day = " << time.Day() << endl; - cout << " Hour = " << time.HourString() << endl; - cout << " Hour = " << time.Hour() << endl; - cout << " Minute = " << time.MinuteString() << endl; - cout << " Minute = " << time.Minute() << endl; - cout << " Second = " << time.SecondString() << endl; - cout << " Second = " << time.Second() << endl; - cout << " Day of Year = " << time.DayOfYearString() << endl; - cout << " Day of Year = " << time.DayOfYear() << endl; - cout << " Et = " << time.EtString() << endl; - cout << " Et = " << time.Et() << endl; - cout << " UTC = " << time.UTC() << endl; - } - catch(IException &error) { - error.print(); - } - - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator>= member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " >= " << t2->EtString() << " = "; - cout << (*t1 >= *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " >= " << t3->EtString() << " = "; - cout << (*t1 >= *t3) << endl; - iTime *t4 = new iTime("2003/01/02 12:15:01.1230"); - cout << " " << t1->EtString() << " >= " << t4->EtString() << " = "; - cout << (*t1 >= *t4) << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator<= member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " <= " << t2->EtString() << " = "; - cout << (*t1 <= *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " <= " << t3->EtString() << " = "; - cout << (*t1 <= *t3) << endl; - iTime *t4 = new iTime("2003/01/02 12:15:01.1230"); - cout << " " << t1->EtString() << " <= " << t4->EtString() << " = "; - cout << (*t1 <= *t4) << endl; - } - catch(IException &error) { - error.print(); - } - - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator> member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " > " << t2->EtString() << " = "; - cout << (*t1 > *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " > " << t3->EtString() << " = "; - cout << (*t1 > *t3) << endl; - iTime *t4 = new iTime("2003/01/02 12:15:01.1230"); - cout << " " << t1->EtString() << " > " << t4->EtString() << " = "; - cout << (*t1 > *t4) << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator< member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " < " << t2->EtString() << " = "; - cout << (*t1 < *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " < " << t3->EtString() << " = "; - cout << (*t1 < *t3) << endl; - iTime *t4 = new iTime("2003/01/02 12:15:01.1230"); - cout << " " << t1->EtString() << " < " << t4->EtString() << " = "; - cout << (*t1 < *t4) << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator!= member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " != " << t2->EtString() << " = "; - cout << (*t1 != *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " != " << t3->EtString() << " = "; - cout << (*t1 != *t3) << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator== member" << endl; - QString test = "2003/01/02 12:15:01.1234"; - iTime *t1 = new iTime(test); - test = "2003/01/02 12:15:01.1234"; - iTime *t2 = new iTime(test); - cout << " " << t1->EtString() << " == " << t2->EtString() << " = "; - cout << (*t1 == *t2) << endl; - iTime *t3 = new iTime("2003/01/02 12:15:01.12345"); - cout << " " << t1->EtString() << " == " << t3->EtString() << " = "; - cout << (*t1 == *t3) << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator+(double) member" << endl; - iTime t1(0.0); - t1 = t1 + 1.01; - cout << " " << t1.EtString() << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator+=(double) member" << endl; - iTime t1(0.0); - t1 += 1.01; - cout << " " << t1.EtString() << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator-(double) member" << endl; - iTime t1(0.0); - t1 = t1 - 1.01; - cout << " " << t1.EtString() << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of iTime operator-=(double) member" << endl; - iTime t1(0.0); - t1 -= 1.01; - cout << " " << t1.EtString() << endl; - } - catch(IException &error) { - error.print(); - } - - try { - cout << endl; - cout << setprecision(9); - cout << " Test of double operator-(iTIme) member" << endl; - iTime t1(0.0); - iTime t2 = 100.001; - double interval = t1 - t2; - cout << " " << interval << endl; - } - catch(IException &error) { - error.print(); - } - -} - - - diff --git a/isis/tests/AlternativeTestCsmModel.cpp b/isis/tests/AlternativeTestCsmModel.cpp index a9c6d4f29d..3c57cc912d 100644 --- a/isis/tests/AlternativeTestCsmModel.cpp +++ b/isis/tests/AlternativeTestCsmModel.cpp @@ -184,7 +184,7 @@ std::string AlternativeTestCsmModel::getSensorMode() const { * @return std::string reference date and time */ std::string AlternativeTestCsmModel::getReferenceDateAndTime() const { - return "AlternativeTestCsmModel_ReferenceDateTime"; + return "20000101T115959Z"; } @@ -490,3 +490,149 @@ std::vector AlternativeTestCsmModel::getCrossCovarianceMatrix( return covariance; } + +// csm::RasterGM methods +csm::ImageCoord AlternativeTestCsmModel::groundToImage(const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::ImageCoord(0.0,0.0); +} + + +csm::ImageCoordCovar AlternativeTestCsmModel::groundToImage(const csm::EcefCoordCovar& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::ImageCoordCovar(0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::EcefCoord AlternativeTestCsmModel::imageToGround(const csm::ImageCoord& imagePt, + double height, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + +csm::EcefCoordCovar AlternativeTestCsmModel::imageToGround(const csm::ImageCoordCovar& imagePt, + double height, + double heightVariance, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefCoordCovar(0.0, 0.0, 0.0); +} + + +csm::EcefLocus AlternativeTestCsmModel::imageToProximateImagingLocus( + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefLocus(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::EcefLocus AlternativeTestCsmModel::imageToRemoteImagingLocus( + const csm::ImageCoord& imagePt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefLocus(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::ImageCoord AlternativeTestCsmModel::getImageStart() const { + return csm::ImageCoord(0.0, 0.0); +} + + +csm::ImageVector AlternativeTestCsmModel::getImageSize() const { + return csm::ImageVector(0.0, 0.0); +} + + +std::pair AlternativeTestCsmModel::getValidImageRange() const { + std::pair csmPair(csm::ImageCoord(0.0, 0.0), csm::ImageCoord(0.0, 0.0)); + return csmPair; +} + + +std::pair AlternativeTestCsmModel::getValidHeightRange() const { + std::pair csmPair(0.0, 0.0); + return csmPair; +} + + +csm::EcefVector AlternativeTestCsmModel::getIlluminationDirection(const csm::EcefCoord& groundPt) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +double AlternativeTestCsmModel::getImageTime(const csm::ImageCoord& imagePt) const { + return 0.0; +} + + +csm::EcefCoord AlternativeTestCsmModel::getSensorPosition(const csm::ImageCoord& imagePt) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + + +csm::EcefCoord AlternativeTestCsmModel::getSensorPosition(double time) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + + +csm::EcefVector AlternativeTestCsmModel::getSensorVelocity(const csm::ImageCoord& imagePt) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +csm::EcefVector AlternativeTestCsmModel::getSensorVelocity(double time) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +csm::RasterGM::SensorPartials AlternativeTestCsmModel::computeSensorPartials( + int index, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::RasterGM::SensorPartials(0.0, 0.0); +} + + +csm::RasterGM::SensorPartials AlternativeTestCsmModel::computeSensorPartials( + int index, + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + + return csm::RasterGM::SensorPartials(0.0, 0.0); +} + + +std::vector AlternativeTestCsmModel::computeGroundPartials(const csm::EcefCoord& groundPt) const { + std::vector vec = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + return vec; +} + +const csm::CorrelationModel& AlternativeTestCsmModel::getCorrelationModel() const { + return m_correlationModel; +} + +std::vector AlternativeTestCsmModel::getUnmodeledCrossCovariance( + const csm::ImageCoord& pt1, + const csm::ImageCoord& pt2) const { + std::vector vec = {0.0, 0.0, 0.0, 0.0}; + return vec; +} + + diff --git a/isis/tests/AlternativeTestCsmModel.h b/isis/tests/AlternativeTestCsmModel.h index d11e532586..b44519883c 100644 --- a/isis/tests/AlternativeTestCsmModel.h +++ b/isis/tests/AlternativeTestCsmModel.h @@ -6,6 +6,8 @@ #include "csm/Plugin.h" #include "csm/GeometricModel.h" #include "csm/Version.h" +#include "csm/RasterGM.h" +#include "csm/CorrelationModel.h" /** * An alternative Test CSM (Community Sensor Model) Sensor Model used to test @@ -15,7 +17,7 @@ * * @author 2020-12-08 Kristin Berry */ -class AlternativeTestCsmModel : public csm::GeometricModel { +class AlternativeTestCsmModel : public csm::RasterGM { public: // Static variables that describe the model static const std::string SENSOR_MODEL_NAME; @@ -73,8 +75,87 @@ class AlternativeTestCsmModel : public csm::GeometricModel { csm::param::Set pSet = csm::param::VALID, const csm::GeometricModel::GeometricModelList& otherModels = GeometricModel::GeometricModelList()) const; +// csm::RasterGM methods + virtual csm::ImageCoord groundToImage(const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::ImageCoordCovar groundToImage(const csm::EcefCoordCovar& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefCoord imageToGround(const csm::ImageCoord& imagePt, + double height, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefCoordCovar imageToGround(const csm::ImageCoordCovar& imagePt, + double height, + double heightVariance, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm:: WarningList* warnings = NULL) const; + + virtual csm::EcefLocus imageToProximateImagingLocus( + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefLocus imageToRemoteImagingLocus( + const csm::ImageCoord& imagePt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::ImageCoord getImageStart() const; + + virtual csm::ImageVector getImageSize() const; + + virtual std::pair getValidImageRange() const; + + virtual std::pair getValidHeightRange() const; + + virtual csm::EcefVector getIlluminationDirection(const csm::EcefCoord& groundPt) const; + + virtual double getImageTime(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefCoord getSensorPosition(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefCoord getSensorPosition(double time) const; + + virtual csm::EcefVector getSensorVelocity(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefVector getSensorVelocity(double time) const; + + virtual SensorPartials computeSensorPartials( + int index, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual SensorPartials computeSensorPartials( + int index, + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual std::vector computeGroundPartials(const csm::EcefCoord& groundPt) const; + virtual const csm::CorrelationModel& getCorrelationModel() const; + virtual std::vector getUnmodeledCrossCovariance( + const csm::ImageCoord& pt1, + const csm::ImageCoord& pt2) const; + private: std::string m_modelState; std::vector m_param_values; + csm::NoCorrelationModel m_correlationModel; }; #endif diff --git a/isis/tests/CSMCameraTests.cpp b/isis/tests/CSMCameraTests.cpp new file mode 100644 index 0000000000..47f4f41315 --- /dev/null +++ b/isis/tests/CSMCameraTests.cpp @@ -0,0 +1,466 @@ +#include +#include + +#include "csm/csm.h" +#include "csm/Ellipsoid.h" + +#include "Fixtures.h" +#include "iTime.h" +#include "Latitude.h" +#include "Longitude.h" +#include "MockCsmPlugin.h" +#include "Mocks.h" +#include "TestUtilities.h" +#include "StringBlob.h" +#include "FileName.h" +#include "Fixtures.h" + +#include +using json = nlohmann::json; + +#include "gmock/gmock.h" + +using namespace Isis; + +TEST_F(CSMCameraFixture, SetImage) { + csm::Ellipsoid wgs84; + EXPECT_CALL(mockModel, imageToRemoteImagingLocus(MatchImageCoord(csm::ImageCoord(4.5, 4.5)), ::testing::_, ::testing::_, ::testing::_)) + .Times(1) + // looking straight down X-Axis + .WillOnce(::testing::Return(csm::EcefLocus(wgs84.getSemiMajorRadius() + 50000, 0, 0, -1, 0, 0))); + EXPECT_CALL(mockModel, getImageTime) + .Times(1) + .WillOnce(::testing::Return(10.0)); + + EXPECT_TRUE(testCam->SetImage(5, 5)); + EXPECT_EQ(testCam->UniversalLatitude(), 0.0); + EXPECT_EQ(testCam->UniversalLongitude(), 0.0); + EXPECT_THAT(testCam->lookDirectionBodyFixed(), ::testing::ElementsAre(-1.0, 0.0, 0.0)); + + iTime refTime("2000-01-01T11:58:55.816"); + EXPECT_EQ((refTime + 10.0).Et(), testCam->time().Et()); +} + + +TEST_F(CSMCameraDemFixture, SetImage) { + EXPECT_CALL(mockModel, imageToRemoteImagingLocus(MatchImageCoord(csm::ImageCoord(4.5, 4.5)), ::testing::_, ::testing::_, ::testing::_)) + .Times(1) + // looking straight down X-Axis + .WillOnce(::testing::Return(csm::EcefLocus(demRadius + 50000, 0, 0, -1, 0, 0))); + EXPECT_CALL(mockModel, computeGroundPartials) + .WillRepeatedly(::testing::Return(std::vector{1, 2, 3, 4, 5, 6})); + EXPECT_CALL(mockModel, getImageTime) + .Times(1) + .WillOnce(::testing::Return(10.0)); + + testCam->SetImage(5, 5); + EXPECT_EQ(testCam->UniversalLatitude(), 0.0); + EXPECT_EQ(testCam->UniversalLongitude(), 0.0); +} + + +TEST_F(CSMCameraFixture, SetGround) { + // Define some things to match/return + csm::Ellipsoid wgs84; + csm::ImageCoord imagePt(4.5, 4.5); + csm::EcefCoord groundPt(wgs84.getSemiMajorRadius(), 0, 0); + csm::EcefLocus imageLocus(wgs84.getSemiMajorRadius() + 50000, 0, 0, -1, 0, 0); + + // Setup expected calls/returns + EXPECT_CALL(mockModel, groundToImage(MatchEcefCoord(groundPt), ::testing::_, ::testing::_, ::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Return(imagePt)); + EXPECT_CALL(mockModel, imageToRemoteImagingLocus(MatchImageCoord(imagePt), ::testing::_, ::testing::_, ::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Return(imageLocus)); + EXPECT_CALL(mockModel, getImageTime) + .Times(4) + .WillRepeatedly(::testing::Return(10.0)); + + iTime refTime("2000-01-01T11:58:55.816"); + + EXPECT_TRUE(testCam->SetGround(Latitude(0.0, Angle::Degrees), Longitude(0.0, Angle::Degrees))); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + EXPECT_EQ((refTime + 10.0).Et(), testCam->time().Et()); + EXPECT_THAT(testCam->lookDirectionBodyFixed(), ::testing::ElementsAre(-1.0, 0.0, 0.0)); + + EXPECT_TRUE(testCam->SetGround(SurfacePoint(Latitude(0.0, Angle::Degrees), + Longitude(0.0, Angle::Degrees), + Distance(wgs84.getSemiMajorRadius(), Distance::Meters)))); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + EXPECT_EQ((refTime + 10.0).Et(), testCam->time().Et()); + EXPECT_THAT(testCam->lookDirectionBodyFixed(), ::testing::ElementsAre(-1.0, 0.0, 0.0)); + + EXPECT_TRUE(testCam->SetUniversalGround(0.0, 0.0)); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + EXPECT_EQ((refTime + 10.0).Et(), testCam->time().Et()); + EXPECT_THAT(testCam->lookDirectionBodyFixed(), ::testing::ElementsAre(-1.0, 0.0, 0.0)); + + EXPECT_TRUE(testCam->SetUniversalGround(0.0, 0.0, wgs84.getSemiMajorRadius())); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + EXPECT_EQ((refTime + 10.0).Et(), testCam->time().Et()); + EXPECT_THAT(testCam->lookDirectionBodyFixed(), ::testing::ElementsAre(-1.0, 0.0, 0.0)); +} + + +TEST_F(CSMCameraDemFixture, SetGround) { + // Define some things to match/return + csm::ImageCoord imagePt(4.5, 4.5); + csm::EcefCoord groundPt(demRadius, 0, 0); + csm::EcefLocus imageLocus(demRadius + 50000, 0, 0, -1, 0, 0); + + // Setup expected calls/returns + EXPECT_CALL(mockModel, groundToImage(MatchEcefCoord(groundPt), ::testing::_, ::testing::_, ::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Return(imagePt)); + EXPECT_CALL(mockModel, imageToRemoteImagingLocus(MatchImageCoord(imagePt), ::testing::_, ::testing::_, ::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Return(imageLocus)); + EXPECT_CALL(mockModel, getImageTime) + .Times(4) + .WillRepeatedly(::testing::Return(10.0)); + + EXPECT_TRUE(testCam->SetGround(Latitude(0.0, Angle::Degrees), Longitude(0.0, Angle::Degrees))); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + + EXPECT_TRUE(testCam->SetGround(SurfacePoint(Latitude(0.0, Angle::Degrees), + Longitude(0.0, Angle::Degrees), + Distance(demRadius, Distance::Meters)))); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + + EXPECT_TRUE(testCam->SetUniversalGround(0.0, 0.0)); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); + + EXPECT_TRUE(testCam->SetUniversalGround(0.0, 0.0, demRadius)); + EXPECT_EQ(testCam->Line(), 5.0); + EXPECT_EQ(testCam->Sample(), 5.0); +} + + +TEST_F(CSMCameraSetFixture, Resolution) { + // Setup to return the ground partials we want + // The pseudoinverse of: + // 1 2 3 + // 4 5 6 + // + // is + // -17 8 + // -2 2 * 1/18 + // 13 -4 + EXPECT_CALL(mockModel, computeGroundPartials) + .Times(6) + .WillRepeatedly(::testing::Return(std::vector{1, 2, 3, 4, 5, 6})); + + // Use expect near here because the psuedoinverse calculation is only accurate to ~1e-10 + double expectedLineRes = sqrt(17*17 + 2*2 + 13*13)/18; + double expectedSampRes = sqrt(8*8 + 2*2 + 4*4)/18; + EXPECT_NEAR(testCam->LineResolution(), expectedLineRes, 1e-10); + EXPECT_NEAR(testCam->ObliqueLineResolution(), expectedLineRes, 1e-10); + EXPECT_NEAR(testCam->SampleResolution(), expectedSampRes, 1e-10); + EXPECT_NEAR(testCam->ObliqueSampleResolution(), expectedSampRes, 1e-10); + EXPECT_NEAR(testCam->DetectorResolution(), (expectedLineRes+expectedSampRes) / 2.0, 1e-10); + EXPECT_NEAR(testCam->ObliqueDetectorResolution(), (expectedLineRes+expectedSampRes) / 2.0, 1e-10); +} + + +TEST_F(CSMCameraSetFixture, InstrumentBodyFixedPosition) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(imageLocus.point)); + + double position[3]; + testCam->instrumentBodyFixedPosition(position); + EXPECT_EQ(position[0], (imageLocus.point.x) / 1000.0); + EXPECT_EQ(position[1], (imageLocus.point.y) / 1000.0); + EXPECT_EQ(position[2], (imageLocus.point.z) / 1000.0); +} + + +TEST_F(CSMCameraSetFixture, SubSpacecraftPoint) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(imageLocus.point)); + + double lat, lon; + testCam->subSpacecraftPoint(lat, lon); + EXPECT_EQ(lat, 0.0); + EXPECT_EQ(lon, 0.0); +} + + +TEST_F(CSMCameraSetFixture, SlantDistance) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(imageLocus.point)); + + double expectedDistance = sqrt( + pow(imageLocus.point.x - groundPt.x, 2) + + pow(imageLocus.point.y - groundPt.y, 2) + + pow(imageLocus.point.z - groundPt.z, 2)) / 1000.0; + EXPECT_DOUBLE_EQ(testCam->SlantDistance(), expectedDistance); +} + + +TEST_F(CSMCameraSetFixture, TargetCenterDistance) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(imageLocus.point)); + + double expectedDistance = sqrt( + pow(imageLocus.point.x, 2) + + pow(imageLocus.point.y, 2) + + pow(imageLocus.point.z, 2)) / 1000.0; + EXPECT_DOUBLE_EQ(testCam->targetCenterDistance(), expectedDistance); +} + + +TEST_F(CSMCameraSetFixture, PhaseAngle) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(csm::EcefCoord(groundPt.x + 50000, groundPt.y, groundPt.z + 50000))); + EXPECT_CALL(mockModel, getIlluminationDirection(MatchEcefCoord(groundPt))) + .Times(1) + .WillOnce(::testing::Return(csm::EcefVector(0.0, 0.0, -1.0))); + + EXPECT_DOUBLE_EQ(testCam->PhaseAngle(), 45.0); +} + + +TEST_F(CSMCameraSetFixture, IncidenceAngle) { + EXPECT_CALL(mockModel, getIlluminationDirection(MatchEcefCoord(groundPt))) + .Times(1) + .WillOnce(::testing::Return(csm::EcefVector(0.0, 0.0, -1.0))); + + EXPECT_DOUBLE_EQ(testCam->IncidenceAngle(), 90.0); +} + + +TEST_F(CSMCameraSetFixture, EmissionAngle) { + EXPECT_CALL(mockModel, getSensorPosition(MatchImageCoord(imagePt))) + .Times(1) + .WillOnce(::testing::Return(imageLocus.point)); + + EXPECT_DOUBLE_EQ(testCam->EmissionAngle(), 0.0); +} + + +TEST_F(CSMCameraFixture, SetTime) { + try + { + testCam->setTime(iTime("2000-01-01T11:58:55.816")); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Setting the image time is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Setting the image time is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, SubSolarPoint) { + try + { + double lat, lon; + testCam->subSolarPoint(lat ,lon); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Sub solar point is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Sub solar point is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, PixelIfovOffsets) { + try + { + testCam->PixelIfovOffsets(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Pixel Field of View is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Pixel Field of View is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, SunPosition) { + try + { + double position[3]; + testCam->sunPosition(position); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Sun position is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Sun position is not supported for CSM camera models\""; + } + + try + { + testCam->sunPosition(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Sun position is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Sun position is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, InstrumentPosition) { + try + { + testCam->instrumentPosition(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Instrument position is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Instrument position is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, BodyRotation) { + try + { + testCam->bodyRotation(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Target body orientation is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Target body orientation is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, InstrumentRotation) { + try + { + testCam->instrumentRotation(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Instrument orientation is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Instrument orientation is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, SolarLongitude) { + try + { + testCam->solarLongitude(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Solar longitude is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Solar longitude is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, SolarDistance) { + try + { + testCam->SolarDistance(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Solar distance is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Solar distance is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, RightAscension) { + try + { + testCam->RightAscension(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Right Ascension is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Right Ascension is not supported for CSM camera models\""; + } +} + + +TEST_F(CSMCameraFixture, Declination) { + try + { + testCam->Declination(); + } + catch(Isis::IException &e) + { + EXPECT_TRUE(e.toString().toLatin1().contains("Declination is not supported " + "for CSM camera models")) << e.toString().toStdString(); + } + catch(...) + { + FAIL() << "Expected an IException with message \"" + " Declination is not supported for CSM camera models\""; + } +} \ No newline at end of file diff --git a/isis/tests/CamptFunctionalTests.cpp b/isis/tests/CamptFunctionalTests.cpp index 9b31e1aad3..82816821b6 100644 --- a/isis/tests/CamptFunctionalTests.cpp +++ b/isis/tests/CamptFunctionalTests.cpp @@ -2,8 +2,12 @@ #include #include +#include "csm/csm.h" +#include "csm/Ellipsoid.h" + #include "campt.h" #include "Fixtures.h" +#include "Mocks.h" #include "Pvl.h" #include "PvlGroup.h" #include "TestUtilities.h" @@ -260,3 +264,56 @@ TEST_F(DefaultCube, FunctionalTestCamptAllowOutside) { EXPECT_DOUBLE_EQ( (double) groundPoint.findKeyword("Sample"), -1.0); EXPECT_DOUBLE_EQ( (double) groundPoint.findKeyword("Line"), -1.0); } + +TEST_F(CSMCubeFixture, FunctionalTestCamptCSMCamera) { + + double pointRadius = csm::Ellipsoid().getSemiMajorRadius(); + // We don't care exactly how the Mock is called so just setup return values + EXPECT_CALL(mockModel, getSensorIdentifier) + .WillRepeatedly(::testing::Return("MockSensorID")); + EXPECT_CALL(mockModel, getPlatformIdentifier) + .WillRepeatedly(::testing::Return("MockPlatformID")); + EXPECT_CALL(mockModel, getReferenceDateAndTime) + .WillRepeatedly(::testing::Return("20000101T115855.816")); + EXPECT_CALL(mockModel, imageToRemoteImagingLocus) + .WillRepeatedly(::testing::Return(csm::EcefLocus(pointRadius + 50000, 0, 0, -1, 0, 0))); + EXPECT_CALL(mockModel, groundToImage(::testing::An(), ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return(csm::ImageCoord(4.5, 4.5))); + EXPECT_CALL(mockModel, computeGroundPartials) + .WillRepeatedly(::testing::Return(std::vector{1, 2, 3, 4, 5, 6})); + EXPECT_CALL(mockModel, getImageTime) + .WillRepeatedly(::testing::Return(10.0)); + EXPECT_CALL(mockModel, computeGroundPartials) + .WillRepeatedly(::testing::Return(std::vector{1, 2, 3, 4, 5, 6})); + EXPECT_CALL(mockModel, getSensorPosition(::testing::An())) + .WillRepeatedly(::testing::Return(csm::EcefCoord(pointRadius + 50000, 0, 0))); + EXPECT_CALL(mockModel, getIlluminationDirection) + .WillRepeatedly(::testing::Return(csm::EcefVector(0.0, 0.0, -1.0))); + + QVector args = {"sample=5", "line=5"}; + UserInterface options(APP_XML, args); + Pvl appLog; + + campt(testCube, options, &appLog); + PvlGroup groundPoint = appLog.findGroup("GroundPoint"); + + // Check that invalid values are all set to null + EXPECT_TRUE(groundPoint.findKeyword("RightAscension").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("Declination").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SunPosition").isNull(0)); + EXPECT_TRUE(groundPoint.findKeyword("SunPosition").isNull(1)); + EXPECT_TRUE(groundPoint.findKeyword("SunPosition").isNull(2)); + EXPECT_TRUE(groundPoint.findKeyword("SubSolarAzimuth").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SolarDistance").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SubSolarLatitude").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SubSolarLongitude").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SubSolarGroundAzimuth").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("LocalSolarTime").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("SolarLongitude").isNull()); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionJ2000").isNull(0)); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionJ2000").isNull(1)); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionJ2000").isNull(2)); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionCamera").isNull(0)); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionCamera").isNull(1)); + EXPECT_TRUE(groundPoint.findKeyword("LookDirectionCamera").isNull(2)); +} diff --git a/isis/tests/CubeTests.cpp b/isis/tests/CubeTests.cpp index 7fa28a40bb..493c99c241 100644 --- a/isis/tests/CubeTests.cpp +++ b/isis/tests/CubeTests.cpp @@ -5,6 +5,7 @@ #include using json = nlohmann::json; +#include "StringBlob.h" #include "Cube.h" #include "Camera.h" @@ -231,3 +232,10 @@ TEST(CubeTest, TestCubeAttachSpiceFromIsd) { EXPECT_PRED_FORMAT2(AssertQStringsEqual, cam->instrumentNameLong(), "Visual Imaging Subsystem Camera B"); } + +TEST_F(SmallCube, TestCubeHasBlob) { + StringBlob testBlob("Test String", "TestBlob"); + testCube->write(testBlob); + EXPECT_TRUE(testCube->hasBlob("String", "TestBlob")); + EXPECT_FALSE(testCube->hasBlob("String", "SomeOtherTestBlob")); +} diff --git a/isis/tests/Fixtures.cpp b/isis/tests/Fixtures.cpp index b425f11415..4098543827 100644 --- a/isis/tests/Fixtures.cpp +++ b/isis/tests/Fixtures.cpp @@ -1,4 +1,5 @@ #include +#include #include "CubeAttribute.h" #include "FileName.h" @@ -7,6 +8,8 @@ #include "Portal.h" #include "LineManager.h" #include "SpecialPixel.h" +#include "StringBlob.h" +#include "TestUtilities.h" #include "ControlNet.h" namespace Isis { @@ -832,4 +835,128 @@ namespace Isis { cubeFileList.write(cubeListPath); } + void CSMCubeFixture::SetUp() { + SmallCube::SetUp(); + + // Instrument group + // Just need a target name + PvlGroup instGroup("Instrument"); + instGroup += PvlKeyword("TargetName", "TestTarget"); + instGroup += PvlKeyword("InstrumentId", "TestId"); + testCube->putGroup(instGroup); + + // Kernels group + // Just need a shapemodel specified + PvlGroup kernGroup("Kernels"); + kernGroup += PvlKeyword("ShapeModel", "Null"); + testCube->putGroup(kernGroup); + + // CSMInfo group + // This just has to exist, but fill it out for completeness and incase it + // ever does matter + PvlGroup infoGroup("CsmInfo"); + infoGroup += PvlKeyword("CSMPlatformID", "TestPlatform"); + infoGroup += PvlKeyword("CSMInstrumentId", "TestInstrument"); + infoGroup += PvlKeyword("ReferenceTime", "2000-01-01T11:58:55.816"); // J2000 epoch + + PvlKeyword paramNames("ModelParameterNames"); + paramNames += "TestNoneParam"; + paramNames += "TestFictitiousParam"; + paramNames += "TestRealParam"; + paramNames += "TestFixedParam"; + PvlKeyword paramUnits("ModelParameterUnits"); + paramUnits += "unitless"; + paramUnits += "m"; + paramUnits += "rad"; + paramUnits += "lines/sec"; + PvlKeyword paramTypes("ModelParameterTypes"); + paramTypes += "NONE"; + paramTypes += "FICTITIOUS"; + paramTypes += "REAL"; + paramTypes += "FIXED"; + + infoGroup += paramNames; + infoGroup += paramUnits; + infoGroup += paramTypes; + + testCube->putGroup(infoGroup); + + // Register the mock with our plugin + std::string mockModelName = QUuid().toString().toStdString(); + MockCsmPlugin loadablePlugin; + loadablePlugin.registerModel(mockModelName, &mockModel); + + // CSMState BLOB + StringBlob csmStateBlob(mockModelName, "CSMState"); + csmStateBlob.Label() += PvlKeyword("ModelName", QString::fromStdString(mockModelName)); + csmStateBlob.Label() += PvlKeyword("PluginName", QString::fromStdString(loadablePlugin.getPluginName())); + testCube->write(csmStateBlob); + filename = testCube->fileName(); + testCube->close(); + testCube->open(filename, "rw"); + } + + void CSMCameraFixture::SetUp() { + CSMCubeFixture::SetUp(); + + // Account for calls that happen while making a CSMCamera + EXPECT_CALL(mockModel, getSensorIdentifier()) + .Times(2) + .WillRepeatedly(::testing::Return("MockSensorID")); + EXPECT_CALL(mockModel, getPlatformIdentifier()) + .Times(2) + .WillRepeatedly(::testing::Return("MockPlatformID")); + EXPECT_CALL(mockModel, getReferenceDateAndTime()) + .Times(1) + .WillRepeatedly(::testing::Return("2000-01-01T11:58:55.816")); + + testCam = testCube->camera(); + } + + void CSMCameraSetFixture::SetUp() { + CSMCameraFixture::SetUp(); + + imagePt = csm::ImageCoord(4.5, 4.5); + groundPt = csm::EcefCoord(wgs84.getSemiMajorRadius(), 0, 0); + imageLocus = csm::EcefLocus(wgs84.getSemiMajorRadius() + 50000, 0, 0, -1, 0, 0); + + // Setup the mock for setImage and ensure it succeeds + EXPECT_CALL(mockModel, imageToRemoteImagingLocus(MatchImageCoord(imagePt), ::testing::_, ::testing::_, ::testing::_)) + .Times(1) + .WillOnce(::testing::Return(imageLocus)); + EXPECT_CALL(mockModel, getImageTime) + .Times(1) + .WillOnce(::testing::Return(10.0)); + + ASSERT_TRUE(testCam->SetImage(5, 5)); // Assert here so that the test code doesn't run if the camera isn't set + } + + void CSMCameraDemFixture::SetUp() { + CSMCubeFixture::SetUp(); + + // Record the demRadius at 0 lat, 0 lon + demRadius = 3394200.43980104; + + // Update the shapemodel on the cube + PvlGroup &kernGroup = testCube->group("Kernels"); + kernGroup.addKeyword(PvlKeyword("ShapeModel", "data/CSMCamera/mola_compressed_prep.cub"), Pvl::Replace); + + // Close and re-open the cube, then save off the new camera + testCube->close(); + testCube->open(filename, "rw"); + + // Account for calls that happen while making a CSMCamera + EXPECT_CALL(mockModel, getSensorIdentifier()) + .Times(2) + .WillRepeatedly(::testing::Return("MockSensorID")); + EXPECT_CALL(mockModel, getPlatformIdentifier()) + .Times(2) + .WillRepeatedly(::testing::Return("MockPlatformID")); + EXPECT_CALL(mockModel, getReferenceDateAndTime()) + .Times(1) + .WillRepeatedly(::testing::Return("2000-01-01T11:58:55.816")); + + testCam = testCube->camera(); + } + } diff --git a/isis/tests/Fixtures.h b/isis/tests/Fixtures.h index 62ba5b7c5d..c085a0c2a4 100644 --- a/isis/tests/Fixtures.h +++ b/isis/tests/Fixtures.h @@ -11,6 +11,9 @@ #include +#include "csm/csm.h" +#include "csm/Ellipsoid.h" + #include "Cube.h" #include "IException.h" #include "OriginalLabel.h" @@ -20,6 +23,8 @@ #include "ImagePolygon.h" #include "PolygonTools.h" #include "Blob.h" +#include "MockCsmPlugin.h" +#include "Mocks.h" #include "ControlNet.h" #include "FileList.h" #include "FileName.h" @@ -180,8 +185,8 @@ namespace Isis { QString ckPath = "data/mroKernels/mroCK.bc"; QString sclkPath = "data/mroKernels/mroSCLK.tsc"; QString lskPath = "data/mroKernels/mroLSK.tls"; - Cube dejitteredCube; - QString jitterPath; + Cube dejitteredCube; + QString jitterPath; void SetUp() override; void setInstrument(QString ikid, QString instrumentId, QString spacecraftName); @@ -208,6 +213,42 @@ namespace Isis { void SetUp() override; }; + + class CSMCubeFixture : public SmallCube { + protected: + QString filename; + MockRasterGM mockModel; + + void SetUp() override; +}; + + +class CSMCameraFixture : public CSMCubeFixture { + protected: + Camera *testCam; + + void SetUp() override; +}; + + +class CSMCameraSetFixture : public CSMCameraFixture { + protected: + csm::Ellipsoid wgs84; + csm::ImageCoord imagePt; + csm::EcefCoord groundPt; + csm::EcefLocus imageLocus; + + void SetUp() override; +}; + + +class CSMCameraDemFixture : public CSMCubeFixture { + protected: + Camera *testCam; + double demRadius; + + void SetUp() override; +}; } #endif diff --git a/isis/tests/FunctionalTestsCsminit.cpp b/isis/tests/FunctionalTestsCsminit.cpp index d256f91574..3f191ba7c3 100644 --- a/isis/tests/FunctionalTestsCsminit.cpp +++ b/isis/tests/FunctionalTestsCsminit.cpp @@ -104,6 +104,11 @@ TEST_F(CSMPluginFixture, CSMInitDefault) { EXPECT_EQ(QString(blobPvl.findKeyword("PluginName")).toStdString(), plugin->getPluginName()); EXPECT_EQ(modelName, TestCsmModel::SENSOR_MODEL_NAME); + // Check the Instrument group + ASSERT_TRUE(testCube->hasGroup("Instrument")); + PvlGroup &instGroup = testCube->group("Instrument"); + EXPECT_TRUE(instGroup.hasKeyword("TargetName")); + // Check the CsmInfo group ASSERT_TRUE(testCube->hasGroup("CsmInfo")); PvlGroup &infoGroup = testCube->group("CsmInfo"); @@ -132,7 +137,7 @@ TEST_F(CSMPluginFixture, CSMInitDefault) { EXPECT_TRUE(kernGroup.hasKeyword("ShapeModel")); } -TEST_F(CSMPluginFixture, CSMinitRunTwice) { +TEST_F(CSMPluginFixture, CSMInitRunTwice) { // Run csminit twice in a row. Verify that end result is as if csminit were // only run once with the second arguments. QVector args = { @@ -188,7 +193,7 @@ TEST_F(CSMPluginFixture, CSMinitRunTwice) { EXPECT_FALSE(kernGroup.hasKeyword("ShapeModel")); } -TEST_F(CSMPluginFixture, CSMinitMultiplePossibleModels) { +TEST_F(CSMPluginFixture, CSMInitMultiplePossibleModels) { // Test csminit when multiple possible models can be created. First, verify that this will fail // without specifying the MODELNAME, then specify the MODELNAME and check the results. @@ -202,9 +207,9 @@ TEST_F(CSMPluginFixture, CSMinitMultiplePossibleModels) { // and/or plugin name. try { csminit(options); - } + } catch (IException &e) { - EXPECT_THAT(e.what(), testing::HasSubstr("Multiple models can be created from the ISD")); + EXPECT_THAT(e.what(), testing::HasSubstr("Multiple models can be created from the ISD")); } // Re-run with the model name specified @@ -260,7 +265,7 @@ TEST_F(CSMPluginFixture, CSMinitMultiplePossibleModels) { } -TEST_F(CSMPluginFixture, CSMinitFails) { +TEST_F(CSMPluginFixture, CSMInitFails) { // Create an ISD that will result in no successful models json isd; isd["name"] = "failing_isd"; @@ -281,16 +286,16 @@ TEST_F(CSMPluginFixture, CSMinitFails) { // Expect a failure due to being unable to construct any model from the isd try { csminit(options); - } + } catch (IException &e) { - EXPECT_THAT(e.what(), testing::HasSubstr("No loaded model could be created from the ISD")); + EXPECT_THAT(e.what(), testing::HasSubstr("No loaded model could be created from the ISD")); } } // This test uses the DefaultCube fixture because it has already has attached // spice data that csminit should remove. -TEST_F(DefaultCube, CSMinitSpiceCleanup) { +TEST_F(DefaultCube, CSMInitSpiceCleanup) { // Create an ISD json isd; isd["test_param_one"] = 1.0; @@ -322,9 +327,51 @@ TEST_F(DefaultCube, CSMinitSpiceCleanup) { } +// This test uses the DefaultCube fixture because it has already been spiceinit'd +TEST_F(DefaultCube, CSMInitSpiceRestoredAfterFailure) { + // Create an ISD + json isd; + isd["test_param_one"] = 1.0; + isd["test_param_two"] = 2.0; + + QString isdPath = tempDir.path() + "/default.json"; + std::ofstream file(isdPath.toStdString()); + file << isd; + file.flush(); + + QVector args = { + "from="+testCube->fileName(), + "isd="+isdPath, + "shape=fake.broken"}; + + QString cubeFile = testCube->fileName(); + + PvlGroup kernelsGroup = testCube->group("Kernels"); + PvlGroup instrumentGroup = testCube->group("Instrument"); + + UserInterface options(APP_XML, args); + + testCube->close(); + + ASSERT_ANY_THROW(csminit(options)); + + Cube outputCube(cubeFile); + ASSERT_NO_THROW(outputCube.camera()); + + EXPECT_TRUE(outputCube.hasTable("InstrumentPointing")); + EXPECT_TRUE(outputCube.hasTable("InstrumentPosition")); + EXPECT_TRUE(outputCube.hasTable("BodyRotation")); + EXPECT_TRUE(outputCube.hasTable("SunPosition")); + ASSERT_TRUE(outputCube.hasGroup("Kernels")); + ASSERT_TRUE(outputCube.hasGroup("Instrument")); + EXPECT_PRED_FORMAT2(AssertPvlGroupEqual, kernelsGroup, outputCube.group("Kernels")); + EXPECT_PRED_FORMAT2(AssertPvlGroupEqual, instrumentGroup, outputCube.group("Instrument")); +} + + // This test uses the DefaultCube fixture because it has already has attached // spice data that csminit should not remove on a failure. -TEST_F(DefaultCube, CSMinitSpiceNoCleanup) { +TEST_F(DefaultCube, CSMInitSpiceNoCleanup) { // Create an ISD that will result in no successful models json isd; isd["test_param_one"] = "value_one"; @@ -350,3 +397,105 @@ TEST_F(DefaultCube, CSMinitSpiceNoCleanup) { // The cube should still be intact and we should still be able to get a camera EXPECT_NO_THROW(outputCube.camera()); } + +TEST_F(CSMPluginFixture, CSMInitStateStringFails) { + // Create an state string that will result in no successful models + json state; + state["name"] = "failing_isd"; + state["test_param_one"] = "value_one"; + state["test_param_does_not_exist"] = "failing_value"; + + QString statePath = tempDir.path() + "/failing.json"; + std::ofstream file(statePath.toStdString()); + file << state; + file.flush(); + + QVector args = { + "from="+filename, + "state="+statePath}; + + UserInterface options(APP_XML, args); + + // Expect a failure due to missing modelname and pluginname + try { + csminit(options); + } + catch (IException &e) { + EXPECT_THAT(e.what(), testing::HasSubstr("When using a State string, PLUGINNAME and MODELNAME must be specified")); + } + + QVector argsWithModel = { + "from="+filename, + "state="+statePath, + "modelname=TestCsmModel", + "pluginname=TestCsmPlugin"}; + + UserInterface optionsWithModel(APP_XML, argsWithModel); + + // Expect a failure due to a bad state string. + try { + csminit(optionsWithModel); + } + catch (IException &e) { + EXPECT_THAT(e.what(), testing::HasSubstr("Could not construct sensor model using STATE string and MODELNAME")); + } + + QVector argsWithIsdAndState = { + "from="+filename, + "isd=fakePath", + "state="+statePath, + "modelname=TestCsmModel", + "pluginname=TestCsmPlugin"}; + + UserInterface optionsWithIsdAndState(APP_XML, argsWithIsdAndState); + + // Expect a failure due to providing both an ISD and a STATE string + try { + csminit(optionsWithIsdAndState); + } + catch (IException &e) { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot enter both [ISD] and [STATE]. Please enter either [ISD] or [STATE]")); + } +} + +TEST_F(CSMPluginFixture, CSMInitWithState) { + // Run csminit with ISD + QVector args = { + "from="+filename, + "isd="+isdPath + }; + + UserInterface options(APP_XML, args); + csminit(options); + + testCube->open(filename); + + // Read blob off csminited cube, get state string and save off to compare + StringBlob state("","CSMState"); + testCube->read(state); + testCube->close(); + + // Write the state out to a file + std::string stateBefore = state.string(); + QString statePath = tempDir.path() + "/state.json"; + std::ofstream stateFile(statePath.toStdString()); + stateFile << stateBefore; + stateFile.flush(); + + // Run csminit with state just saved to file + QVector argsState = { + "from="+filename, + "state="+statePath, + "modelname=TestCsmModel", + "pluginname=TestCsmPlugin"}; + + UserInterface optionsState(APP_XML, argsState); + csminit(optionsState); + + // Pull state string off. if ending state string = original state string these are functionally equiv. + testCube->open(filename); + + StringBlob stateAfter("","CSMState"); + testCube->read(stateAfter); + EXPECT_STREQ(stateBefore.c_str(), stateAfter.string().c_str()); +} diff --git a/isis/tests/FunctionalTestsSpiceinit.cpp b/isis/tests/FunctionalTestsSpiceinit.cpp index a1eca46431..416329c3f2 100644 --- a/isis/tests/FunctionalTestsSpiceinit.cpp +++ b/isis/tests/FunctionalTestsSpiceinit.cpp @@ -2,7 +2,10 @@ #include #include +#include + #include "spiceinit.h" +#include "csminit.h" #include "Cube.h" #include "CubeAttribute.h" @@ -16,6 +19,8 @@ #include "Fixtures.h" +using json = nlohmann::json; + #include "gmock/gmock.h" using namespace Isis; @@ -656,3 +661,47 @@ TEST_F(DemCube, FunctionalTestSpiceinitWebAndShapeModel) { PvlGroup kernels = testCube.label()->findGroup("Kernels", Pvl::Traverse); EXPECT_PRED_FORMAT2(AssertQStringsEqual, kernels.findKeyword("ShapeModel"), demCube->fileName()); } + + +TEST_F(SmallCube, FunctionalTestSpiceinitCsminitRestorationOnFail) { + // csminit the cube + + // Create an ISD + json isd; + isd["test_param_one"] = 1.0; + isd["test_param_two"] = 2.0; + + QString isdPath = tempDir.path() + "/default.json"; + std::ofstream file(isdPath.toStdString()); + file << isd; + file.flush(); + + QString cubeFile = testCube->fileName(); + + QVector csmArgs = { + "from="+cubeFile, + "isd="+isdPath}; + + QString CSMINIT_APP_XML = FileName("$ISISROOT/bin/xml/csminit.xml").expanded(); + UserInterface csmOptions(CSMINIT_APP_XML, csmArgs); + testCube->close(); + + ASSERT_NO_THROW(csminit(csmOptions)); + testCube->open(cubeFile); + PvlGroup csmInfoGroup = testCube->group("CsmInfo"); + testCube->close(); + + // spiceinit + QVector spiceinitArgs = {"from="+cubeFile}; + + UserInterface spiceinitOptions(APP_XML, spiceinitArgs); + ASSERT_ANY_THROW(spiceinit(spiceinitOptions)); + + Cube outputCube(cubeFile); + + ASSERT_NO_THROW(outputCube.camera()); + EXPECT_TRUE(outputCube.hasBlob("String", "CSMState")); + ASSERT_TRUE(outputCube.hasGroup("CsmInfo")); + EXPECT_PRED_FORMAT2(AssertPvlGroupEqual, csmInfoGroup, outputCube.group("CsmInfo")); +} + diff --git a/isis/tests/LinearAlgebraTests.cpp b/isis/tests/LinearAlgebraTests.cpp new file mode 100644 index 0000000000..8b12d58af7 --- /dev/null +++ b/isis/tests/LinearAlgebraTests.cpp @@ -0,0 +1,80 @@ +#include "LinearAlgebra.h" +#include "IException.h" + +#include + +using namespace Isis; + +TEST(LinearAlgebraTest, pseudoinverseInvertible) { + LinearAlgebra::Matrix mat(2,2); + mat(0,0) = 1.0; + mat(0,1) = 2.0; + mat(1,0) = 3.0; + mat(1,1) = 4.0; + + LinearAlgebra::Matrix inverse = LinearAlgebra::pseudoinverse(mat); + + EXPECT_TRUE(LinearAlgebra::isIdentity(LinearAlgebra::multiply(inverse, mat))); + EXPECT_TRUE(LinearAlgebra::isIdentity(LinearAlgebra::multiply(mat, inverse))); +} + +TEST(LinearAlgebraTest, pseudoinverseRightInverse) { + LinearAlgebra::Matrix mat(2,3); + mat(0,0) = 1.0; + mat(0,1) = 2.0; + mat(0,2) = 3.0; + mat(1,0) = 4.0; + mat(1,1) = 5.0; + mat(1,2) = 6.0; + + LinearAlgebra::Matrix inverse = LinearAlgebra::pseudoinverse(mat); + + EXPECT_TRUE(LinearAlgebra::isIdentity(LinearAlgebra::multiply(mat, inverse))); +} + +TEST(LinearAlgebraTest, pseudoinverseLeftInverse) { + LinearAlgebra::Matrix mat(3,2); + mat(0,0) = 1.0; + mat(0,1) = 2.0; + mat(1,0) = 3.0; + mat(1,1) = 4.0; + mat(2,0) = 5.0; + mat(2,1) = 6.0; + + LinearAlgebra::Matrix inverse = LinearAlgebra::pseudoinverse(mat); + + EXPECT_TRUE(LinearAlgebra::isIdentity(LinearAlgebra::multiply(inverse, mat))); +} + +TEST(LinearAlgebraTest, pseudoinverseSingular) { + LinearAlgebra::Matrix mat(3,2); + mat(0,0) = 1.0; + mat(0,1) = 2.0; + mat(1,0) = 3.0; + mat(1,1) = 2.0; + mat(2,0) = 4.0; + mat(2,1) = 6.0; + + LinearAlgebra::Matrix inverse = LinearAlgebra::pseudoinverse(mat); + + // Checks based on properties of the pseudoinverse + // M is the matrix + // M^+ is the psuedo inverse of M + // M^T is the transpose of M + + // Check M * M^+ * M - M = 0 + LinearAlgebra::Matrix firstProd = LinearAlgebra::multiply(mat, LinearAlgebra::multiply(inverse, mat)); + EXPECT_TRUE(LinearAlgebra::isZero(firstProd - mat)); + + // Check M^+ * M * M^+ - M^+ = 0 + LinearAlgebra::Matrix secondProd = LinearAlgebra::multiply(inverse, LinearAlgebra::multiply(mat, inverse)); + EXPECT_TRUE(LinearAlgebra::isZero(secondProd - inverse)); + + // Check (M * M^+)^T = M * M^+ + LinearAlgebra::Matrix thirdProd = LinearAlgebra::multiply(mat, inverse); + EXPECT_TRUE(LinearAlgebra::isZero(LinearAlgebra::transpose(thirdProd) - thirdProd)); + + // Check (M^+ * M)^T = M^+ * M + LinearAlgebra::Matrix fourthProd = LinearAlgebra::multiply(inverse, mat); + EXPECT_TRUE(LinearAlgebra::isZero(LinearAlgebra::transpose(fourthProd) - fourthProd)); +} diff --git a/isis/tests/MockCsmPlugin.cpp b/isis/tests/MockCsmPlugin.cpp new file mode 100644 index 0000000000..748aa8d444 --- /dev/null +++ b/isis/tests/MockCsmPlugin.cpp @@ -0,0 +1,261 @@ +#include + +#include "MockCsmPlugin.h" + +// Static Instance of itself +const MockCsmPlugin MockCsmPlugin::m_registeredPlugin; + +// Declaration of static variables +const std::string MockCsmPlugin::PLUGIN_NAME = "MockCsmPlugin"; +const std::string MockCsmPlugin::MANUFACTURER_NAME = "MockCsmPluginCreator"; +const std::string MockCsmPlugin::RELEASE_DATE = "20210201"; +QMap MockCsmPlugin::m_registeredModels = QMap(); + +/** + * Default constructor + */ +MockCsmPlugin::MockCsmPlugin() { + // deliberately blank for testing +} + + +/** + * Default destructor + */ +MockCsmPlugin::~MockCsmPlugin() {} + + +/** + * Gets the name of the plugin. + * + * @return std::string name of the plugin + */ +std::string MockCsmPlugin::getPluginName() const { + return MockCsmPlugin::PLUGIN_NAME; +} + + +/** + * Gets the name of the manufacturer of the plugin. + * + * @return std::string the name of the manufacturer of the plugin + */ +std::string MockCsmPlugin::getManufacturer() const { + return MockCsmPlugin::MANUFACTURER_NAME; +} + + +/** + * Gets the release date of the plugin. + * + * @return std::string release date + */ +std::string MockCsmPlugin::getReleaseDate() const { + return MockCsmPlugin::RELEASE_DATE; +} + + +/** + * Returns the version of CSM the Plugin uses. + * + * @return csm::Version CSM version plugin uses + */ +csm::Version MockCsmPlugin::getCsmVersion() const { + return csm::Version(3,0,3); +} + + +/** + * Returns the number of sensor models in the plugin + * + * @return size_t Number of sensor models in the plugin + */ +size_t MockCsmPlugin::getNumModels() const { + // This will change as models are loaded into the plugin, + // and we cannot access the model registry by index so just say it's empty + return 0; +} + + +/** + * Returns the model name at the given index. + * + * @param modelIndex The index number for the sensor model + * + * @return std::string model name + */ +std::string MockCsmPlugin::getModelName(size_t modelIndex) const { + // We cannot access the model registry by index so just return a dummy value + return "Dummy Model Name"; +} + + +/** + * Returns the sensor model family at the given index. + * + * @param modelIndex the index number for the sensor model family + * + * @return std::string sensor model family + */ +std::string MockCsmPlugin::getModelFamily(size_t modelIndex) const { + // We cannot access the model registry by index so just return a dummy value + return "TestModelFamily"; +} + + +/** + * Returns the CSM sensor model version for a given model. + * + * @param modelName the model name + * + * @return csm::Version the version of the csm sensor model + */ +csm::Version MockCsmPlugin::getModelVersion(const std::string& modelName) const { + return csm::Version(1,0,0); +} + + +/** + * Tests if the sensor model can be created from a given state. + * + * @param modelName the model name + * @param modelState the model state + * @param warnings the warning list + * + * @return bool if the model can be constructed from the state + */ +bool MockCsmPlugin::canModelBeConstructedFromState(const std::string& modelName, + const std::string& modelState, + csm::WarningList* warnings) const { + return m_registeredModels.contains(modelState); +} + + +/** + * Checks to see if the CSM sensor model can be constructed from + * a given ISD. + * + * @param imageSupportData isd for the image + * @param modelName name of the sensor model + * @param warnings warnings list + * + * @return bool true if the model can be constructed from the isd + */ +bool MockCsmPlugin::canModelBeConstructedFromISD( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings) const { + // Do not do anything with ISDs so that we don't interfere with csminit testing + return false; +} + + +/** + * True if the ISD can be converted to a state. + * + * @param imageSupportData the image support data + * @param modelName the name of the model + * @param warnings the warnings list + * + * @return bool true if the ISD can be converted to a state. + */ +bool MockCsmPlugin::canISDBeConvertedToModelState( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings) const{ + // Do not do anything with ISDs so that we don't interfere with csminit testing + return false; +} + + +/** + * Conver an ISD (Image Support Data) to a model state. + * + * @param imageSupportData The ISD + * @param modelName The name of the model. + * @param warnings The warnings list. + * + * @return std::string the model state converted from the ISD + */ +std::string MockCsmPlugin::convertISDToModelState( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings) const { + // Do not do anything with ISDs so return a dummy value + return "Dummy model state"; +} + + +/** + * Extracts and returns the model name from the model state. + * + * @param modelState State of the sensor model + * @param warnings The warnings list + * + * @return std::string The sensor model name + */ +std::string MockCsmPlugin::getModelNameFromModelState( + const std::string& modelState, + csm::WarningList* warnings) const { + // The state strings are just the model name so return that + return modelState; +} + + +/** + * Register a new model with the plugin. + * The model can be accessed by calling constructModelFromState with the modelName + * as the state string. + * + * @param modelName The name of the model to be used to access it + * @param model The model to register + */ +void MockCsmPlugin::registerModel(std::string modelName, csm::Model* model) { + m_registeredModels.insert(modelName, model); +} + + +/** + * Creates and returns a sensor model from a state string. + * This will access the internal model registry and return a pointer to the model + * whose name is the state string. This will also remove the model from the model registry. + * + * @param modelState State of the sensor model + * @param warnings The csm warnings list + * + * @return csm::Model* The constructed sensor model + */ +csm::Model* MockCsmPlugin::constructModelFromState( + const std::string& modelState, + csm::WarningList* warnings) const { + // QMap::take will return a default value if the key is not in the map + // so check that it's there before calling take. + if (m_registeredModels.contains(modelState)) { + return m_registeredModels.take(modelState); + } + csm::Error::ErrorType errorType = csm::Error::SENSOR_MODEL_NOT_SUPPORTED; + std::string msg = "MockCsmPlugin failed to construct model from State"; + std::string func = "MockCsmPlugin::constructModelFromState"; + throw csm::Error(errorType, msg, func); +} + + +/** + * Constructs and returns a sensor model from an ISD. + * + * @param imageSupportData The image support data for an image + * @param modelName The sensor model name + * + * @return csm::Model* the model constructed from the ISD + */ +csm::Model* MockCsmPlugin::constructModelFromISD( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* ) const { + // Do not do anything with ISDs so that we don't interfere with csminit testing + csm::Error::ErrorType errorType = csm::Error::SENSOR_MODEL_NOT_SUPPORTED; + std::string msg = "TstCsmPlugin does not support constructing modeles from ISD"; + std::string func = "MockCsmPlugin::constructModelFromIsd"; + throw csm::Error(errorType, msg, func); +} + diff --git a/isis/tests/MockCsmPlugin.h b/isis/tests/MockCsmPlugin.h new file mode 100644 index 0000000000..7d15ff15cf --- /dev/null +++ b/isis/tests/MockCsmPlugin.h @@ -0,0 +1,83 @@ +#ifndef MockCsmPlugin_h +#define MockCsmPlugin_h + +#include + +#include + +#include "csm/Plugin.h" +#include "csm/Model.h" +#include "csm/Version.h" + +#include + +/** + * Test Community Sensor Model (CSM) plugin class to use for loading specific camera models. + */ +class MockCsmPlugin : public csm::Plugin { + public: + // Static variables that describe the plugin + static const std::string PLUGIN_NAME; + static const std::string MANUFACTURER_NAME; + static const std::string RELEASE_DATE; + static const int N_SENSOR_MODELS; + + MockCsmPlugin(); + ~MockCsmPlugin(); + + std::string getPluginName() const; + + std::string getManufacturer() const; + + std::string getReleaseDate() const; + + csm::Version getCsmVersion() const; + + size_t getNumModels() const; + + std::string getModelName(size_t modelIndex) const; + + std::string getModelFamily(size_t modelIndex) const; + + csm::Version getModelVersion(const std::string& modelName) const; + + bool canModelBeConstructedFromState(const std::string& modelName, + const std::string& modelState, + csm::WarningList* warnings = NULL) const; + + bool canModelBeConstructedFromISD( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings = NULL) const; + + bool canISDBeConvertedToModelState( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings = NULL) const; + + std::string convertISDToModelState( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings = NULL) const; + + csm::Model* constructModelFromState( + const std::string& modelState, + csm::WarningList* warnings = NULL) const; + + csm::Model* constructModelFromISD( + const csm::Isd& imageSupportData, + const std::string& modelName, + csm::WarningList* warnings = NULL) const; + + std::string getModelNameFromModelState( + const std::string& modelState, + csm::WarningList* warnings = NULL) const; + + void registerModel(std::string stateString, csm::Model* model); + + private: + static const MockCsmPlugin m_registeredPlugin; //! static instance of plugin + static QMap m_registeredModels; +}; + +#endif diff --git a/isis/tests/Mocks.h b/isis/tests/Mocks.h index 12a3c3d48a..5a627ad54b 100644 --- a/isis/tests/Mocks.h +++ b/isis/tests/Mocks.h @@ -3,6 +3,10 @@ #include "gmock/gmock.h" +#include "csm/CorrelationModel.h" +#include "csm/csm.h" +#include "csm/RasterGM.h" + #include "Cube.h" #include "Camera.h" #include "Interpolator.h" @@ -28,8 +32,8 @@ class MockCamera : public Camera { public: MockCamera(Cube &cube): Camera(cube) {} MOCK_METHOD(bool, SetImage, (const double sample, const double line), (override)); - MOCK_METHOD(double, Line, (), (override)); - MOCK_METHOD(double, Sample, (), (override)); + MOCK_METHOD(double, Line, (), (const, override)); + MOCK_METHOD(double, Sample, (), (const, override)); MOCK_METHOD(double, UniversalLatitude, (), (const override)); MOCK_METHOD(double, UniversalLongitude, (), (const override)); MOCK_METHOD(bool, SetUniversalGround, (const double latitude, const double longitude), (override)); @@ -81,4 +85,94 @@ public: }; +// Mock CSM Model class +class MockRasterGM : public csm::RasterGM { + public: + // csm::Model + MOCK_METHOD(csm::Version, getVersion, (), (const, override)); + MOCK_METHOD(std::string, getModelName, (), (const, override)); + MOCK_METHOD(std::string, getPedigree, (), (const, override)); + MOCK_METHOD(std::string, getImageIdentifier, (), (const, override)); + MOCK_METHOD(void, setImageIdentifier, (const std::string&, csm::WarningList*), (override)); + MOCK_METHOD(std::string, getSensorIdentifier, (), (const, override)); + MOCK_METHOD(std::string, getPlatformIdentifier, (), (const, override)); + MOCK_METHOD(std::string, getCollectionIdentifier, (), (const, override)); + MOCK_METHOD(std::string, getTrajectoryIdentifier, (), (const, override)); + MOCK_METHOD(std::string, getSensorType, (), (const, override)); + MOCK_METHOD(std::string, getSensorMode, (), (const, override)); + MOCK_METHOD(std::string, getReferenceDateAndTime, (), (const, override)); + MOCK_METHOD(std::string, getModelState, (), (const, override)); + MOCK_METHOD(void, replaceModelState, (const std::string&), (override)); + // csm::GeometricModel methods + MOCK_METHOD(csm::EcefCoord, getReferencePoint, (), (const, override)); + MOCK_METHOD(void, setReferencePoint, (const csm::EcefCoord&), (override)); + MOCK_METHOD(int, getNumParameters, (), (const, override)); + MOCK_METHOD(std::string, getParameterName, (int), (const, override)); + MOCK_METHOD(std::string, getParameterUnits, (int), (const, override)); + MOCK_METHOD(bool, hasShareableParameters, (), (const, override)); + MOCK_METHOD(bool, isParameterShareable, (int), (const, override)); + MOCK_METHOD(csm::SharingCriteria, getParameterSharingCriteria, (int), (const, override)); + MOCK_METHOD(double, getParameterValue, (int), (const, override)); + MOCK_METHOD(void, setParameterValue, (int, double), (override)); + MOCK_METHOD(csm::param::Type, getParameterType, (int), (const, override)); + MOCK_METHOD(void, setParameterType, (int, csm::param::Type), (override)); + MOCK_METHOD(double, getParameterCovariance, (int, int), (const, override)); + MOCK_METHOD(void, setParameterCovariance, (int, int, double), (override)); + MOCK_METHOD(int, getNumGeometricCorrectionSwitches, (), (const, override)); + MOCK_METHOD(std::string, getGeometricCorrectionName, (int), (const, override)); + MOCK_METHOD(void, setGeometricCorrectionSwitch, (int, bool, csm::param::Type), (override)); + MOCK_METHOD(bool, getGeometricCorrectionSwitch, (int), (const, override)); + MOCK_METHOD(std::vector, + getCrossCovarianceMatrix, + (const csm::GeometricModel&, csm::param::Set, const csm::GeometricModel::GeometricModelList&), + (const, override)); + // RasterGM methods + MOCK_METHOD(csm::ImageCoord, groundToImage, (const csm::EcefCoord&, double, double*, csm::WarningList*), (const, override)); + MOCK_METHOD(csm::ImageCoordCovar, + groundToImage, + (const csm::EcefCoordCovar&, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::EcefCoord, + imageToGround, + (const csm::ImageCoord&, double, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::EcefCoordCovar, + imageToGround, + (const csm::ImageCoordCovar&, double, double, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::EcefLocus, + imageToProximateImagingLocus, + (const csm::ImageCoord&, const csm::EcefCoord&, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::EcefLocus, + imageToRemoteImagingLocus, + (const csm::ImageCoord&, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::ImageCoord, getImageStart, (), (const, override)); + MOCK_METHOD(csm::ImageVector, getImageSize, (), (const, override)); + MOCK_METHOD((std::pair), getValidImageRange, (), (const, override)); + MOCK_METHOD((std::pair), getValidHeightRange, (), (const, override)); + MOCK_METHOD(csm::EcefVector, getIlluminationDirection, (const csm::EcefCoord&), (const, override)); + MOCK_METHOD(double, getImageTime, (const csm::ImageCoord&), (const, override)); + MOCK_METHOD(csm::EcefCoord, getSensorPosition, (const csm::ImageCoord&), (const, override)); + MOCK_METHOD(csm::EcefCoord, getSensorPosition, (double), (const, override)); + MOCK_METHOD(csm::EcefVector, getSensorVelocity, (const csm::ImageCoord&), (const, override)); + MOCK_METHOD(csm::EcefVector, getSensorVelocity, (double), (const, override)); + MOCK_METHOD(csm::RasterGM::SensorPartials, + computeSensorPartials, + (int, const csm::EcefCoord&, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(csm::RasterGM::SensorPartials, + computeSensorPartials, + (int, const csm::ImageCoord&, const csm::EcefCoord&, double, double*, csm::WarningList*), + (const, override)); + MOCK_METHOD(std::vector, computeGroundPartials, (const csm::EcefCoord&), (const, override)); + MOCK_METHOD(const csm::CorrelationModel&, getCorrelationModel, (), (const, override)); + MOCK_METHOD(std::vector, + getUnmodeledCrossCovariance, + (const csm::ImageCoord&, const csm::ImageCoord&), + (const, override)); +}; + + #endif diff --git a/isis/tests/TestCsmModel.cpp b/isis/tests/TestCsmModel.cpp index 1c7645d2c5..dc4acf6cd5 100644 --- a/isis/tests/TestCsmModel.cpp +++ b/isis/tests/TestCsmModel.cpp @@ -175,7 +175,7 @@ std::string TestCsmModel::getSensorMode() const { * @return std::string reference date and time */ std::string TestCsmModel::getReferenceDateAndTime() const { - return "TestCsmModel_ReferenceDateTime"; + return "20000101T115959Z"; } @@ -484,4 +484,147 @@ std::vector TestCsmModel::getCrossCovarianceMatrix( return covariance; } +// csm::RasterGM methods +csm::ImageCoord TestCsmModel::groundToImage(const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::ImageCoord(0.0,0.0); +} + + +csm::ImageCoordCovar TestCsmModel::groundToImage(const csm::EcefCoordCovar& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::ImageCoordCovar(0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::EcefCoord TestCsmModel::imageToGround(const csm::ImageCoord& imagePt, + double height, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + +csm::EcefCoordCovar TestCsmModel::imageToGround(const csm::ImageCoordCovar& imagePt, + double height, + double heightVariance, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefCoordCovar(0.0, 0.0, 0.0); +} + + +csm::EcefLocus TestCsmModel::imageToProximateImagingLocus( + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefLocus(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::EcefLocus TestCsmModel::imageToRemoteImagingLocus( + const csm::ImageCoord& imagePt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::EcefLocus(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +} + + +csm::ImageCoord TestCsmModel::getImageStart() const { + return csm::ImageCoord(0.0, 0.0); +} + + +csm::ImageVector TestCsmModel::getImageSize() const { + return csm::ImageVector(0.0, 0.0); +} + + +std::pair TestCsmModel::getValidImageRange() const { + std::pair csmPair(csm::ImageCoord(0.0, 0.0), csm::ImageCoord(0.0, 0.0)); + return csmPair; +} + + +std::pair TestCsmModel::getValidHeightRange() const { + std::pair csmPair(0.0, 0.0); + return csmPair; +} + + +csm::EcefVector TestCsmModel::getIlluminationDirection(const csm::EcefCoord& groundPt) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +double TestCsmModel::getImageTime(const csm::ImageCoord& imagePt) const { + return 0.0; +} + + +csm::EcefCoord TestCsmModel::getSensorPosition(const csm::ImageCoord& imagePt) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + + +csm::EcefCoord TestCsmModel::getSensorPosition(double time) const { + return csm::EcefCoord(0.0, 0.0, 0.0); +} + + +csm::EcefVector TestCsmModel::getSensorVelocity(const csm::ImageCoord& imagePt) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +csm::EcefVector TestCsmModel::getSensorVelocity(double time) const { + return csm::EcefVector(0.0, 0.0, 0.0); +} + + +csm::RasterGM::SensorPartials TestCsmModel::computeSensorPartials( + int index, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + return csm::RasterGM::SensorPartials(0.0, 0.0); +} + + +csm::RasterGM::SensorPartials TestCsmModel::computeSensorPartials( + int index, + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision, + double* achievedPrecision, + csm::WarningList* warnings) const { + + return csm::RasterGM::SensorPartials(0.0, 0.0); +} + + +std::vector TestCsmModel::computeGroundPartials(const csm::EcefCoord& groundPt) const { + std::vector vec = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + return vec; +} + +const csm::CorrelationModel& TestCsmModel::getCorrelationModel() const { + return m_correlationModel; +} + +std::vector TestCsmModel::getUnmodeledCrossCovariance( + const csm::ImageCoord& pt1, + const csm::ImageCoord& pt2) const { + std::vector vec = {0.0, 0.0, 0.0, 0.0}; + return vec; +} diff --git a/isis/tests/TestCsmModel.h b/isis/tests/TestCsmModel.h index 2ea6edfa6d..cb0ffb2cf0 100644 --- a/isis/tests/TestCsmModel.h +++ b/isis/tests/TestCsmModel.h @@ -3,9 +3,10 @@ #include -#include "csm/GeometricModel.h" +#include "csm/RasterGM.h" #include "csm/Plugin.h" #include "csm/Version.h" +#include "csm/CorrelationModel.h" #include @@ -15,7 +16,7 @@ * * @author 2020-12-08 Kristin Berry */ -class TestCsmModel : public csm::GeometricModel { +class TestCsmModel : public csm::RasterGM { public: // Static variables that describe the model static const std::string SENSOR_MODEL_NAME; @@ -26,6 +27,7 @@ class TestCsmModel : public csm::GeometricModel { TestCsmModel(); ~TestCsmModel(); + // csm::Model methods std::string getFamily() const; csm::Version getVersion() const; @@ -44,6 +46,7 @@ class TestCsmModel : public csm::GeometricModel { std::string getModelState() const; void replaceModelState(const std::string& argState); std::string constructStateFromIsd(const csm::Isd stringIsd); + // csm::GeometricModel methods csm::EcefCoord getReferencePoint() const; void setReferencePoint(const csm::EcefCoord& groundPt); @@ -73,7 +76,86 @@ class TestCsmModel : public csm::GeometricModel { csm::param::Set pSet = csm::param::VALID, const csm::GeometricModel::GeometricModelList& otherModels = GeometricModel::GeometricModelList()) const; + // csm::RasterGM methods + virtual csm::ImageCoord groundToImage(const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::ImageCoordCovar groundToImage(const csm::EcefCoordCovar& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefCoord imageToGround(const csm::ImageCoord& imagePt, + double height, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefCoordCovar imageToGround(const csm::ImageCoordCovar& imagePt, + double height, + double heightVariance, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm:: WarningList* warnings = NULL) const; + + virtual csm::EcefLocus imageToProximateImagingLocus( + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::EcefLocus imageToRemoteImagingLocus( + const csm::ImageCoord& imagePt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual csm::ImageCoord getImageStart() const; + + virtual csm::ImageVector getImageSize() const; + + virtual std::pair getValidImageRange() const; + + virtual std::pair getValidHeightRange() const; + + virtual csm::EcefVector getIlluminationDirection(const csm::EcefCoord& groundPt) const; + + virtual double getImageTime(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefCoord getSensorPosition(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefCoord getSensorPosition(double time) const; + + virtual csm::EcefVector getSensorVelocity(const csm::ImageCoord& imagePt) const; + + virtual csm::EcefVector getSensorVelocity(double time) const; + + virtual SensorPartials computeSensorPartials( + int index, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual SensorPartials computeSensorPartials( + int index, + const csm::ImageCoord& imagePt, + const csm::EcefCoord& groundPt, + double desiredPrecision = 0.001, + double* achievedPrecision = NULL, + csm::WarningList* warnings = NULL) const; + + virtual std::vector computeGroundPartials(const csm::EcefCoord& groundPt) const; + virtual const csm::CorrelationModel& getCorrelationModel() const; + virtual std::vector getUnmodeledCrossCovariance( + const csm::ImageCoord& pt1, + const csm::ImageCoord& pt2) const; + private: std::vector m_param_values; //! Parameter values associated with the sensor model + csm::NoCorrelationModel m_correlationModel; }; #endif diff --git a/isis/tests/TestUtilities.cpp b/isis/tests/TestUtilities.cpp index 133e87031e..a4a3dc29a3 100644 --- a/isis/tests/TestUtilities.cpp +++ b/isis/tests/TestUtilities.cpp @@ -151,20 +151,20 @@ namespace Isis { // Check to see if a QString contains only numeric values. bool isNumeric(QString str){ - QRegExp re("-*\\d*.*\\d*"); + QRegExp re("-*\\d*.*\\d*"); return re.exactMatch(str); } // Compares CSV lines - void compareCsvLine(CSVReader::CSVAxis csvLine, QString headerStr, int initialIndex) { + void compareCsvLine(CSVReader::CSVAxis csvLine, QString headerStr, int initialIndex) { QStringList compareMe = headerStr.split(","); for (int i=initialIndex; i MatchImageCoord(const csm::ImageCoord &expected) { + return ::testing::AllOf( + ::testing::Field(&csm::ImageCoord::line, ::testing::DoubleNear(expected.line, 0.0001)), + ::testing::Field(&csm::ImageCoord::samp, ::testing::DoubleNear(expected.samp, 0.0001)) + ); +} + + + // Matches a CSM ECEF Coord for gMock + ::testing::Matcher MatchEcefCoord(const csm::EcefCoord &expected) { + return ::testing::AllOf( + ::testing::Field(&csm::EcefCoord::x, ::testing::DoubleNear(expected.x, 0.0001)), + ::testing::Field(&csm::EcefCoord::y, ::testing::DoubleNear(expected.y, 0.0001)), + ::testing::Field(&csm::EcefCoord::z, ::testing::DoubleNear(expected.z, 0.0001)) + ); + } } diff --git a/isis/tests/TestUtilities.h b/isis/tests/TestUtilities.h index 2cf584081f..aa63524a12 100644 --- a/isis/tests/TestUtilities.h +++ b/isis/tests/TestUtilities.h @@ -1,13 +1,15 @@ #ifndef TestUtilities_h #define TestUtilities_h -#include "gtest/gtest.h" +#include "gmock/gmock.h" #include #include #include +#include "csm.h" + #include "IException.h" #include "PvlGroup.h" @@ -54,6 +56,9 @@ namespace Isis { void compareCsvLine(CSVReader::CSVAxis csvLine, QString headerStr, int initialIndex=0); void compareCsvLine(CSVReader::CSVAxis csvLine, CSVReader::CSVAxis csvLine2, int initialIndex=0, double tolerance = 0.000001); + + ::testing::Matcher MatchImageCoord(const csm::ImageCoord &expected); + ::testing::Matcher MatchEcefCoord(const csm::EcefCoord &expected); } #endif diff --git a/isis/tests/data/CSMCamera/mola_compressed_prep.cub b/isis/tests/data/CSMCamera/mola_compressed_prep.cub new file mode 100644 index 0000000000000000000000000000000000000000..bf09ec0c290eb84be200d904e17c830a4d4d0e27 GIT binary patch literal 75871 zcmeZI%1TWxQLt6;EY2);E=@}1Qc!@4Ip-IF_zDWaC5c5PPL(C83JMCg3TCFJ#^ziK z3JMBt`9-;jB_I|^JR~y*q8!8sPRuRHNi9~eRWLFHs|5-AWagz7gUv8B6@!Hp6fpEFD4^(uSn8CRmjX2qtj{$sB_3=fs_6ll6{$HelS3*C zK>h$3V5<}OQA%nN$VWcKNigBWVuU7RV@opw0|Pw+sHAUc zPDy4#PG)M6f~|rfR2bnD1qBcr5~?6~fgPw|tKgehP>`9I4mKp9C?6CNnfZBsiMbG) zK#l{2p>t(UW?o8BW^!TeQ5nLI_ zAO%|mV`FnOBTxvSs`W`M0lNt$hy!vG^HNLllT-6biZYYIX2Kosl3A3RT#}ier(mlP zkYAixl3AAOnpj+drYb)-F*6TjwXK4&86?zwGxIWYOLJj1LL_xT&IjxDO{~C_M#w@n zBZZ-@0xUjZT2Um8&0x}@1qG=^KB;LX&iO@osYMas$k5!t!r08rz(~Octfg2#B{jV$H5H^4lqSI`kqg9vrA`Q~V5{Jhn3M`i zZ=n1D%Eh2eZ-!lINMaH=<0Dd=twL}{VnM2JeoAUia7kiGW^qYoGAQXG3KCE;Vrgl~ z1<7%s6bQ<2hGtv}3PGvK`9(mb6s>vY!%!xQ*$6W7i_t$0yJ4b z(knP6fYYL_f=hlW#FYvP!I@R5pp41|$(UfXkn<&`p^(Hxx}jJ@)+4jHB)_PVOF;qb zW^kOLl)or}WMK|2KS5yt3nX(xQ$y@A;a`-Qo|%`J1B*OjEC8iiv{*1QH$aJM*Sr)i zhEXsY0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*AuwV<0|SxJ zXI;Tswe{R>{{_{#nIJwR(xKqz0QAY`?jhb zE;}W5J=#^U>%rtG7oVw>&)j zF#m1+Bbf(257|DH|F!sY>1*Cg;fJ3dls=pDb;8d~#*fU?*-Ae!e0BN7`g!T6ukTyl zCBDD=SCqF%pz43nU$(CvA6dR0`R?{tlhuGDnmv)Vfpsa<3M-vEURg%FADh1`e_i*D|8vCGwiK& z?fNk5Q|V)cNADj`y?Oc2yQ4~XKRsZ0=609;+0+N8pPs#!@s#)Z_vcF=%{bN!M4#GsPXEZsqv1z0 z5B)wk>(Gy5lE-!*y?&VU#Es)8j~+VebHwAw?IZPv1PYkx~x4V zvsXDdI|sWMN4Xgo7#J9W2oUY(72sp+>1Gh%5^Mw$Q?OMCc5?}h_i=P`_2J^u*HUoH z%t=)UE-5NaE-5WaRnXGs3UYP!4|0hQi41U6zzb{@+&m*(UE+OQ{oF%5VEUXQLtKLi z=rc7m;&Su!agB%AMM$}kfswhXi7^-0)x_wrRj`DZ9~|NsLckXawhGQZu8w|OaUPz* zA^t&;c-;gO2(4@o$~R>9BT&y`C-A;dAr-8BSjdbd^Z zbqopydCt8kzqCNX7M$GUojm=xK<0qbK8O?VALQxo>F4MZ4-!$ZRWL-AMs)z(Vgj!r(V3bqRRrN!yR`kBR<#VPtJi6x2p z1u4ZwAZ~m~QDRKcGGVnNB#&mX4;d>!3g<9!_i;sb*Gyv61aYu}t%9p#a0pnPw`*iLs5%4%CRi16(PXO-;N$4$8sZO%=O9mK zunvTQ!I8lsu81N55@p6_pmIq;0a{?h!wd$=!m+J_fgU(444{e~BS57KF2xoWD5}F< zgF{?{{Cxd`LlEABnT>8V$ftM|8=IlKBp^P>HQ3)L6cn{EL!q>-f}s(rQ$Q&p*xAts zOF<1vNCtW)X2u2<#-Kt=K>?%~k!;XfAhrs@o}daU-Z|38)6XTy)7jAn9NwU?cMZby zDA-N53WgS_9ze1H7W4?4ut%t;pDR*wj`w$S3wDK-JhlqDpgPW2&lD10U?)MUQXG10 z6?Bb^4U8>ObOgD&xdyrVIlE$uV)Q6OQVdJHV9$aHbj4_a8sr}WE*8LQ70?yK!qPDW zoFyQNU?i$yMEM0`L7OtJc_~~F01;5IRe+T;TnY-F#hJxnsYS)0h780|TLmR!JwrW1 zV}%+}S!S+lV4-Vdti+|D5Kxq#UX+-Nq#Rt@fmIlQN;{~6fW(pvh*|I=t{@v)&OwX@ z7kT!X^Jm-;LT;i9Q zn+i1>T%#rBBqnF4#c#OLJ~m1M*hXXX~fTUg{ImgpsyCV_Q?_=CLw)&W)ol>tQ_ zq#6RTAhCwjBF#-K$jt}YNrkvG1BWuG4y9GxSy-8Zno=}~JLjU*#FA8nM1|b^;>65k zh0@~8ymW=!!~%taqI^)(J~ag#am7T%93)AA3P6nbP*Cvo4|eo)hAM%^F~nAAlqo0# z1bO-gd4@o`Dz*y#ej)w=;8=DJaz%7_Y!xa~K~blm0BQ!Y9u*%`YG5dh^$0Z6Puix1z#qT+y@#Jtp!#G=X|a8Y7lU| z!O6(Y(+A#MwpGY4Eh)}SNd?Owcb{w(LV`j;Eqnz9k06*kKrTTDdtw##!LHf|n`%&L zh$r=CB<7`1w_r2^m0UC_7)`AVEUgScnS%zY*CR16MWG-kF&R`iA}TakK}kxIB{D@~ zD<6yVOG`51(-RBe>DJRPAQYM$K}iEqL_!iYI6ZMMH%^g1SaV=H{RPgLLJP zWFX3M6tjL1vmt5ERslQ&0!h7~F%^g~$ao`Y7r+lxVj_egW}_9Q&@@dTIi_XiMoCVkkpV=Xp#kAR2AIka14}Ct3oBF5kQcV(m6DoT zqE}v|2O6!_OV2OEG1icklvq@hT2g{*upuNPvl!Gj1r4GofI=#9np3Q&;0ZDf9%;yo%wmPS{1Sz-#GK3&aAjJWr%-}yLw=q@ zVqRrQ2B_PTR9d2t2(n6#s45mVGM$VVw}FgLgJxm$Q&Mw_^+Clgp_B_Qpdr;TcBLlJ z0-8V_n_QHSGgT?zPCy2hx`qb2Mxb;~S^_fEH89dOGA1GcK~;tr8d@2cS{Z`|!LTKu z%)HX<;*8R4G82w-QGP)&$sJB&0$xD@97o`Eg-^XHG@=v~5Je7Tf>1#L(Zoh>Uk7{o zK?h7=T})7I3$_R}zy~hA{6IqB#GwC^aOusf{Rg!1_o_G$vN2Mph;?N;E^Mw834NAQB!pq2f!UX3#WBpfJfz zEHKK&-7hoIGuAUu09R2);3zdP#9hJ|nu12h4MFJ^YY77yCN9>`%1tat&Cv(V7#iy* zm1gFo;2rvgT5B9)Xk=w%WCc!&*b0@kg4I{?C%!_o#vys3?wa~o9G&t>KcQ5LZgIkVg;T;qIN + +#include "IString.h" +#include "iTime.h" + +#include "gmock/gmock.h" + +using namespace Isis; + +TEST(iTimeTests, TimeAccess) { + iTime testTime("2003-01-02T12:15:01.1234"); + + EXPECT_EQ(2003, testTime.Year()); + EXPECT_EQ(01, testTime.Month()); + EXPECT_EQ(02, testTime.Day()); + EXPECT_EQ(12, testTime.Hour()); + EXPECT_EQ(15, testTime.Minute()); + EXPECT_EQ(01.1234, testTime.Second()); + EXPECT_EQ(2, testTime.DayOfYear()); + EXPECT_NEAR(94781765.307363, testTime.Et(), 0.000001); +} + + +TEST(iTimeTests, TimeWithSlashes) { + QString timeString("2003-01-02T12:15:01.1234"); + QString timeStringWithSlashes("2003/01/02 12:15:01.1234"); + iTime testTime(timeString); + iTime testTimeWithSlashes(timeStringWithSlashes); + + EXPECT_EQ(testTimeWithSlashes.Year(), testTime.Year()); + EXPECT_EQ(testTimeWithSlashes.Month(), testTime.Month()); + EXPECT_EQ(testTimeWithSlashes.Day(), testTime.Day()); + EXPECT_EQ(testTimeWithSlashes.Hour(), testTime.Hour()); + EXPECT_EQ(testTimeWithSlashes.Minute(), testTime.Minute()); + EXPECT_EQ(testTimeWithSlashes.Second(), testTime.Second()); + EXPECT_EQ(testTimeWithSlashes.DayOfYear(), testTime.DayOfYear()); + EXPECT_EQ(testTimeWithSlashes.Et(), testTime.Et()); +} + + +TEST(iTimeTests, FromEt) { + QString timeString("2003-01-02T12:15:01.1234"); + iTime testTime(timeString); + iTime testTimeFromEt(testTime.Et()); + + EXPECT_EQ(testTimeFromEt.Year(), testTime.Year()); + EXPECT_EQ(testTimeFromEt.Month(), testTime.Month()); + EXPECT_EQ(testTimeFromEt.Day(), testTime.Day()); + EXPECT_EQ(testTimeFromEt.Hour(), testTime.Hour()); + EXPECT_EQ(testTimeFromEt.Minute(), testTime.Minute()); + EXPECT_EQ(testTimeFromEt.Second(), testTime.Second()); + EXPECT_EQ(testTimeFromEt.DayOfYear(), testTime.DayOfYear()); + EXPECT_EQ(testTimeFromEt.Et(), testTime.Et()); +} + + +TEST(iTimeTests, StringTimes) { + QString timeString("2003-01-02T12:15:01.1234"); + iTime testTime(timeString); + + EXPECT_EQ(toString(testTime.Year()).toStdString(), testTime.YearString().toStdString()); + EXPECT_EQ(toString(testTime.Month()).toStdString(), testTime.MonthString().toStdString()); + EXPECT_EQ(toString(testTime.Day()).toStdString(), testTime.DayString().toStdString()); + EXPECT_EQ(toString(testTime.Hour()).toStdString(), testTime.HourString().toStdString()); + EXPECT_EQ(toString(testTime.Minute()).toStdString(), testTime.MinuteString().toStdString()); + EXPECT_EQ(toString(testTime.Second(), 8).toStdString(), testTime.SecondString(8).toStdString()); + EXPECT_EQ(toString(testTime.DayOfYear()).toStdString(), testTime.DayOfYearString().toStdString()); + EXPECT_EQ(toString(testTime.Et()).toStdString(), testTime.EtString().toStdString()); + EXPECT_EQ(timeString.toStdString(), testTime.UTC().toStdString()); +} + + +TEST(iTimeTests, Comparison) { + QString beforeString("2003-01-02T12:15:01.1234"); + QString afterString("2010-04-03T16:32:56.2487"); + + iTime beforeTime(beforeString); + iTime afterTime(afterString); + + EXPECT_TRUE(beforeTime < afterTime); + EXPECT_FALSE(afterTime < beforeTime); + EXPECT_FALSE(beforeTime < beforeTime); + + EXPECT_TRUE(beforeTime <= afterTime); + EXPECT_FALSE(afterTime <= beforeTime); + EXPECT_TRUE(beforeTime <= beforeTime); + + EXPECT_FALSE(beforeTime > afterTime); + EXPECT_TRUE(afterTime > beforeTime); + EXPECT_FALSE(beforeTime > beforeTime); + + EXPECT_FALSE(beforeTime >= afterTime); + EXPECT_TRUE(afterTime >= beforeTime); + EXPECT_TRUE(beforeTime >= beforeTime); + + EXPECT_TRUE(beforeTime != afterTime); + EXPECT_TRUE(afterTime != beforeTime); + EXPECT_FALSE(beforeTime != beforeTime); + + EXPECT_FALSE(beforeTime == afterTime); + EXPECT_FALSE(afterTime == beforeTime); + EXPECT_TRUE(beforeTime == beforeTime); +} + + +TEST(iTimeTests, Arithmetic) { + iTime testTime("2003-01-02T12:15:01.1234"); + + EXPECT_EQ((testTime + 10.0).Et(), testTime.Et() + 10.0); + EXPECT_EQ((10.0 + testTime).Et(), testTime.Et() + 10.0); + EXPECT_EQ((testTime - 10.0).Et(), testTime.Et() - 10.0); + // This doesn't make sense because subtraction isn't commutative + // but that's how the function is written + EXPECT_EQ((10.0 - testTime).Et(), testTime.Et() - 10.0); + + EXPECT_EQ(iTime(20.0) - iTime(10.0), 10.0); + + iTime moreTime = testTime; + moreTime += 10.0; + EXPECT_EQ(moreTime.Et(), testTime.Et() + 10.0); + + iTime lessTime = testTime; + lessTime -= 10.0; + EXPECT_EQ(lessTime.Et(), testTime.Et() - 10.0); +} + + +// googletest parameterized test class to hold two UTC strings. +// The first string is the expected string formatted as YYYY-MM-DDThh:mm:ss.zzzz +// The second string is the input string for setUTC +class SetUTC : public ::testing::TestWithParam> {}; + + +TEST_P(SetUTC, CheckOutput) { + QString expectedString = QString::fromStdString(GetParam().first); + iTime expectedTime(expectedString); + iTime testTime; + + QString inputString = QString::fromStdString(GetParam().second); + testTime.setUtc(inputString); + + EXPECT_EQ(expectedTime.Year(), testTime.Year()); + EXPECT_EQ(expectedTime.Month(), testTime.Month()); + EXPECT_EQ(expectedTime.Day(), testTime.Day()); + EXPECT_EQ(expectedTime.Hour(), testTime.Hour()); + EXPECT_EQ(expectedTime.Minute(), testTime.Minute()); + EXPECT_EQ(expectedTime.Second(), testTime.Second()); + EXPECT_EQ(expectedTime.DayOfYear(), testTime.DayOfYear()); + EXPECT_NEAR(expectedTime.Et(), testTime.Et(), 0.000001); +} + + +INSTANTIATE_TEST_SUITE_P(iTimeTests, SetUTC, ::testing::Values( + std::make_pair("2003-01-02T12:15:01.1234", "2003-01-02T12:15:01.1234"), + std::make_pair("2003-01-02T12:15:01.1234", "20030102T121501.1234"), + std::make_pair("2003-01-02T12:15:01.1234", "200302T121501.1234"), + std::make_pair("2003-01-02T12:15:01.1234", "2003-02T12:15:01.1234"), + std::make_pair("2003-05-02T12:15:01.1234", "2003122T121501.1234"), + std::make_pair("2003-05-02T12:15:01.1234", "2003-122T12:15:01.1234"), + std::make_pair("2003-01-02T12:15:01", "20030102T121501"), + std::make_pair("2003-01-02T12:15:01", "2003-01-02T12:15:01"), + std::make_pair("2003-01-02T12:15:00", "20030102T1215"), + std::make_pair("2003-01-02T12:15:00", "2003-01-02T12:15"), + std::make_pair("2003-01-02T12:00:00", "20030102T12"), + std::make_pair("2003-01-02T12:00:00", "2003-01-02T12"), + std::make_pair("2003-01-02T00:00:00", "20030102T"), + std::make_pair("2003-01-02T00:00:00", "2003-01-02T"))); -- GitLab