Unverified Commit 88112b96 authored by Jesse Mapel's avatar Jesse Mapel Committed by GitHub
Browse files

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: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>

* 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: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>

* 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: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>

* 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: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>

* 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: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>

* 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: default avatarKristin <kberry@usgs.gov>
Co-authored-by: default avatarKristin Berry <kberry@gyro.wr.usgs.gov>
parent cf27c7c1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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

+3 −0
Original line number Diff line number Diff line
@@ -220,6 +220,9 @@ End_Group
    <change name="Kaitlyn Lee" date="2018-02-16">
      Removed units on look direction vectors to make them unitless. Fixes #5125.
    </change>
    <change name="Kristin Berry, Jesse Mapel, and Stuart Sides" date="2021-02-10">
      Modified to output Nulls when using a CSM Camera model and some values are not available.
    </change>
  </history>

  <oldName>
+267 −93
Original line number Diff line number Diff line
@@ -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,42 +48,58 @@ 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;
    // 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_);
    }
    catch(...) {
      // Noop

    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_);
    }

    else if (ui.WasEntered("ISD")) {
      QString isdFilePath = ui.GetFileName("ISD");

      QList<QStringList> possibleModels;
      for (const csm::Plugin * plugin : csm::Plugin::getList()) {
      QString pluginName = QString::fromStdString(plugin->getPluginName());
      if (ui.WasEntered("PLUGINNAME") && pluginName != ui.GetString("PLUGINNAME")) {
        QString currentPluginName = QString::fromStdString(plugin->getPluginName());
        if (ui.WasEntered("PLUGINNAME") && currentPluginName != ui.GetString("PLUGINNAME")) {
          continue;
        }

        for (size_t modelIndex = 0; modelIndex < plugin->getNumModels(); modelIndex++) {
        QString modelName = QString::fromStdString(plugin->getModelName(modelIndex));
        if (ui.WasEntered("MODELNAME") && modelName != ui.GetString("MODELNAME")) {
          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, modelName.toStdString())) {
          QStringList modelSpec = {pluginName, modelName, QString::fromStdString(fileIsd.format())};
          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 others
            continue; // If the file 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())};
          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 others
            continue; // If the NITF 2.1 ISD works, don't check the other ISD formats
          }
        }
      }
@@ -100,10 +118,10 @@ namespace Isis {
        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());
          QString currentPluginName = 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";
            message += "Plugin [" + currentPluginName + "], Model [" + modelName + "]\n";
          }
        }
        throw IException(IException::User, message, _FILEINFO_);
@@ -111,36 +129,105 @@ namespace Isis {

      // 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;

      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 (modelSpec[2] == QString::fromStdString(fileIsd.format())) {
      model = plugin->constructModelFromISD(fileIsd, modelSpec[1].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_);
      }
    else if (modelSpec[2] == QString::fromStdString(nitf21Isd.format())) {
      model = plugin->constructModelFromISD(nitf21Isd, modelSpec[1].toStdString());
      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 = "Invalid ISD format specifications [" + modelSpec[2] + "].";
        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();

    // Add the TargetName to the instrument group, if specified:
    if (ui.WasEntered("TARGETNAME")) {
    // 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");
    }

    if (cube->hasGroup("Kernels")) {
      originalKernels = cube->group("Kernels");
    }

    if (cube->hasGroup("CsmInfo")) {
      originalCsmInfo = cube->group("CsmInfo");
    }

    if (!cube->hasGroup("Instrument")) {
      cube->putGroup(PvlGroup("Instrument"));
    }
    PvlGroup &instrumentGroup = cube->group("Instrument");
    if (ui.WasEntered("TARGETNAME")) {
      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<csm::GeometricModel*>(model);

    if (modelWithParams) {
      PvlKeyword paramNames("ModelParameterNames");
      PvlKeyword paramUnits("ModelParameterUnits");
@@ -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,7 +358,51 @@ namespace Isis {
      kernelsGroup.deleteKeyword("Tolerance");
    }

    // Remove tables from spiceinit

    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");

    // Remove tables from spiceinit before writing to the cube
    cube->deleteBlob("Table", "InstrumentPointing");
    cube->deleteBlob("Table", "InstrumentPosition");
    cube->deleteBlob("Table", "BodyRotation");
@@ -278,25 +410,67 @@ namespace Isis {
    cube->deleteBlob("Table", "CameraStatistics");
    cube->deleteBlob("Polygon", "Footprint");

    if (cube->label()->hasObject("NaifKeywords")) {
      cube->label()->deleteObject("NaifKeywords");
    // 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", modelName);
    blobLabel += PvlKeyword("PluginName", pluginName);
    cube->write(csmStateBlob);

    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);
      }

      cube->deleteGroup("Kernels");
      if (originalKernels.keywords() != 0) {
        cube->putGroup(originalKernels);
      }

      cube->deleteGroup("CsmInfo");
      if (originalCsmInfo.keywords() != 0) {
        cube->putGroup(originalCsmInfo);
      }

      cube->deleteBlob("String", "CSMState");

    // Create our CSM State blob as a string
    // 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()));
      // Restore the original blobs
      if (originalCsmStateBlob.Size() != 0) {
        cube->write(originalCsmStateBlob);
      }

    // Write CSM State blob to cube
    cube->write(csmStateBlob);
      if (originalInstrumentPointing.Records() != 0) {
        cube->write(originalInstrumentPointing);
      }

    // TODO attempt to get the CSM Model from the cube
      if (originalInstrumentPosition.Records() != 0) {
        cube->write(originalInstrumentPosition);
      }

    p.WriteHistory(*cube);
      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_);
    }
  }
}
+25 −3
Original line number Diff line number Diff line
@@ -40,6 +40,15 @@
    <change name="Jesse Mapel" date="2020-11-18">
      Original Version
    </change>
    <change name="Jesse Mapel and Stuart Sides" date="2021-02-03">
      Modified to always add a TargetName and Instrument group to the label for the camera model.
    </change>
    <change name="Kristin Berry and Stuart Sides" date="2021-02-10">
      Modified to revert label and Cube changes on failure.
    </change>
    <change name="Kristin Berry and Stuart Sides" date="2021-02-12">
      Added the ability to take a state string instead of an ISD.
    </change>
  </history>

  <groups>
@@ -61,6 +70,7 @@
      <parameter name="ISD">
        <type>filename</type>
        <fileMode>input</fileMode>
        <internalDefault>none</internalDefault>
        <brief>
          The Instrument Support Data (ISD) file that will be used.
        </brief>
@@ -69,18 +79,30 @@
          model state string. The ISD can be just a filename ISD or a NITF2.1 ISD.
        </description>
      </parameter>

      <parameter name="STATE">
        <type>filename</type>
        <internalDefault>none</internalDefault>
        <fileMode>input</fileMode>
        <brief>
          A Community Sensor Model (CSM) state string
        </brief>
        <description>
          A file containing a Community Sensor Model (CSM) state string that will be used to create the
          model for the input cube.
        </description>
      </parameter>
    </group>

    <group name="Target Specification">
      <parameter name="TARGETNAME">
        <type>string</type>
        <internalDefault>none</internalDefault>
        <internalDefault>Current TargetName in input label or Unknown if not present.</internalDefault>
        <brief>
          The name of the body observed by the image.
        </brief>
        <description>
          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.
        </description>
      </parameter>

+16 −2
Original line number Diff line number Diff line
@@ -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;
Loading