Skip to content
1 change: 1 addition & 0 deletions changes/6004.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ISDLIST parameter to jigsaw for loading CSM cameras from external ISD or model state files instead of cube blobs. Fix OUTPUT_ADJUSTED_CSMSTATE to write the actual adjusted state instead of regenerating an unadjusted one via ale. Add CSMCamera constructor that takes csm::RasterGM* directly, eliminating the model-state-model round-trip in CreateFromIsd.
22 changes: 7 additions & 15 deletions isis/src/base/apps/cam2map/AspMapProjection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ GeoRef::GeoRef(const std::string &projStr, bool /*fromString*/):
// Identity GeoTransform: pixel coords = projected coords
m_gt[0] = 0.0; m_gt[1] = 1.0; m_gt[2] = 0.0;
m_gt[3] = 0.0; m_gt[4] = 0.0; m_gt[5] = -1.0;
(void)GDALInvGeoTransform(m_gt, m_inv_gt);
if (!GDALInvGeoTransform(m_gt, m_inv_gt))
throw IException(IException::Unknown, "Non-invertible geo transform", _FILEINFO_);

OGRSpatialReference projCRS;
projCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
Expand Down Expand Up @@ -257,7 +258,8 @@ void GeoRef::lonlat_to_point(double lon, double lat,
void GeoRef::setGeoTransform(const double gt[6]) {
for (int i = 0; i < 6; i++)
m_gt[i] = gt[i];
(void)GDALInvGeoTransform(m_gt, m_inv_gt);
if (!GDALInvGeoTransform(m_gt, m_inv_gt))
throw IException(IException::Unknown, "Non-invertible geo transform", _FILEINFO_);
}

bool GeoRef::isGeographic() const {
Expand Down Expand Up @@ -1924,20 +1926,10 @@ void renderMapprojectedImage(Camera *cam,
outCube.close();
}

// Load a CSM camera model from an ISD file and set it on the cube,
// so that cube->camera() returns a CSMCamera. Uses the CSMCamera
// constructor that takes plugin/model/state strings directly,
// avoiding the blob serialize/deserialize round-trip.
// Load a CSM camera model from an ISD file and set it in the cube,
// so that cube->camera() returns a CSMCamera.
void loadCsmCamera(const QString &isdFile, Cube *cube) {
CameraFactory::initPlugin();
QStringList spec = CameraFactory::getModelSpecFromIsd(isdFile);
csm::Model *model = CameraFactory::constructModelFromIsd(
isdFile, spec[0], spec[1], spec[2]);
std::string stateStr = model->getModelState();
delete model;

Camera *cam = new CSMCamera(*cube, spec[0], spec[1],
QString::fromStdString(stateStr));
Camera *cam = CameraFactory::CreateFromIsd(isdFile, *cube);
cube->setCamera(cam);
}

Expand Down
29 changes: 29 additions & 0 deletions isis/src/base/objs/CSMCamera/CSMCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,25 @@ void sanitize(std::string &input);
}


/**
* Construct a CSMCamera from an already-constructed CSM model. The model
* pointer is left unmanaged (pre-existing issue).
* This avoids the model->state->model round-trip in CreateFromIsd.
*
* @param cube The cube with the image data
* @param model The CSM RasterGM model
*/
CSMCamera::CSMCamera(Cube &cube, csm::RasterGM *model) : Camera(cube) {
if (!model) {
QString msg = "Null CSM model pointer passed to CSMCamera for image ["
+ cube.fileName() + "].";
throw IException(IException::Programmer, msg, _FILEINFO_);
}
m_model = model;
initFromModel(cube);
}


/**
* Init method which performs most of the setup for the CSM Camera Model inside ISIS.
*
Expand Down Expand Up @@ -118,7 +137,17 @@ void sanitize(std::string &input);
QString msg = "Failed to convert CSM Model to RasterGM.";
throw IException(IException::Programmer, msg, _FILEINFO_);
}
initFromModel(cube);
}


/**
* Set up CSMCamera metadata from an already-assigned m_model.
* Extracts sensor/platform names, reference time, and target.
*
* @param cube The cube with the image data (for target setup)
*/
void CSMCamera::initFromModel(Cube &cube) {
m_instrumentNameLong = QString::fromStdString(m_model->getSensorIdentifier());
m_instrumentNameShort = QString::fromStdString(m_model->getSensorIdentifier());
m_spacecraftNameLong = QString::fromStdString(m_model->getPlatformIdentifier());
Expand Down
9 changes: 7 additions & 2 deletions isis/src/base/objs/CSMCamera/CSMCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ namespace Isis {
// constructors
CSMCamera(Cube &cube);
CSMCamera(Cube &cube, QString pluginName, QString modelName, QString stateString);
CSMCamera(Cube &cube, csm::RasterGM *model);

//! Destroys the CSMCamera object.
~CSMCamera() {};


/**
* The CSM camera needs a bogus type for now.
*
Expand Down Expand Up @@ -144,8 +145,12 @@ namespace Isis {

private:
void init(Cube &cube, QString pluginName, QString modelName, QString stateString);
void initFromModel(Cube &cube);

csm::RasterGM *m_model; //! CSM sensor model
// Not owned. Cannot be freed because test mocks pass stack-allocated
// objects via constructModelFromState(). A proper fix would require
// heap-allocating the mock model in the test fixtures.
csm::RasterGM *m_model = nullptr;
iTime m_refTime; //! The reference time that all model image times are relative to

void isisToCsmPixel(double line, double sample, csm::ImageCoord &csmPixel) const;
Expand Down
148 changes: 146 additions & 2 deletions isis/src/base/objs/CameraFactory/CameraFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,157 @@ namespace Isis {
}

/**
* Create a CSMCamera from an external ISD file and attach it to a cube.
*
* @param isdFile Path to the external ISD file.
* @param cube The cube to attach the camera to.
*
* @return Camera* The CSMCamera object created.
*/
Camera *CameraFactory::CreateFromIsd(const QString &isdFile, Cube &cube) {
// This raw pointer is not managed. Pre-existing issue.
csm::Model *model = constructModelFromIsdOrState(isdFile);

updateLabelForCsm(cube, model);

// Cast to RasterGM
csm::RasterGM *rasterModel = dynamic_cast<csm::RasterGM*>(model);
if (!rasterModel) {
delete model;
QString msg = "CSM model from [" + isdFile + "] is not a RasterGM.";
throw IException(IException::Programmer, msg, _FILEINFO_);
}
return new CSMCamera(cube, rasterModel);
}


/**
* Update the cube label in memory to support CSM cameras.
* Follows the same logic as csminit: sets ShapeModel=Null in the
* Kernels group and injects a CsmInfo group with platform/instrument
* identifiers derived from the CSM model. This allows serial number
* derivation and camera creation to work as if csminit had been run,
* but without modifying the cube on disk.
*
* @param cube The cube whose label will be modified.
* @param model The CSM sensor model.
*/
void CameraFactory::updateLabelForCsm(Cube &cube, csm::Model *model) {
if (!model) return;
PvlObject &cubeObj = cube.label()->findObject("IsisCube");

// Ensure Kernels group exists and has ShapeModel=Null.
// This matches csminit behavior.
if (!cubeObj.hasGroup("Kernels")) {
cubeObj.addGroup(PvlGroup("Kernels"));
}
PvlGroup &kernels = cubeObj.findGroup("Kernels");
kernels.addKeyword(PvlKeyword("ShapeModel", "Null"), Pvl::Replace);

// Add Target group if it doesn't exist. Needed for Target resolution.
// Get TargetName from the Instrument group (same approach as csminit).
if (!cubeObj.hasGroup("Target")) {
QString targetName = "Unknown";
if (cubeObj.hasGroup("Instrument") &&
cubeObj.findGroup("Instrument").hasKeyword("TargetName"))
targetName = cubeObj.findGroup("Instrument")["TargetName"][0];
PvlGroup target("Target");
target += PvlKeyword("TargetName", targetName);
cubeObj.addGroup(target);
}

// Add CsmInfo group
if (cubeObj.hasGroup("CsmInfo"))
cubeObj.deleteGroup("CsmInfo");

PvlGroup infoGroup("CsmInfo");
infoGroup += PvlKeyword("TargetName", cubeObj.findGroup("Target")["TargetName"][0]);
infoGroup += PvlKeyword("CSMPlatformID", QString::fromStdString(model->getPlatformIdentifier()));
infoGroup += PvlKeyword("CSMInstrumentId", QString::fromStdString(model->getSensorIdentifier()));
infoGroup += PvlKeyword("ReferenceTime", QString::fromStdString(model->getReferenceDateAndTime()));
cubeObj.addGroup(infoGroup);
}


/**
* Write adjusted CSM model state to a JSON file. The output file is
* named <prefix><cube_basename>.adjusted_state.json, following the
* same convention as ASP bundle_adjust.
*
* @param cubeFile Path to the input cube (basename used for output name).
* @param state The adjusted CSM model state string.
* @param prefix Optional file prefix (e.g., "run/run_").
*/
void CameraFactory::writeAdjustedCsmState(const QString &cubeFile,
const QString &state,
const QString &prefix) {
QFileInfo fi(cubeFile);
QString outPath = prefix + fi.completeBaseName() + ".adjusted_state.json";

std::ofstream outFile(outPath.toStdString().c_str());
if (!outFile.is_open()) {
QString msg = "Error creating adjusted CSM state file [" + outPath + "].";
throw IException(IException::User, msg, _FILEINFO_);
}
outFile << state.toStdString() << "\n";
outFile.close();
}


/**
* Construct a CSM model from either an ISD or a model state file.
* Tries the ISD path first (canModelBeConstructedFromISD), then
* falls back to the state path (canModelBeConstructedFromState).
* Follows the same plugin iteration logic as csminit.
*
* @param filePath Path to the ISD or model state file.
*
* @return csm::Model* The constructed CSM model (caller owns).
*/
csm::Model *CameraFactory::constructModelFromIsdOrState(
const QString &filePath) {
initPlugin();

// Try ISD path first
csm::Isd isd(filePath.toStdString());
for (const csm::Plugin *plugin : csm::Plugin::getList())
for (size_t j = 0; j < plugin->getNumModels(); j++) {
std::string modelName = plugin->getModelName(j);
if (plugin->canModelBeConstructedFromISD(isd, modelName))
return plugin->constructModelFromISD(isd, modelName);
}

// ISD path failed. Try interpreting the file as a model state string.
std::ifstream stateFile(filePath.toStdString());
if (!stateFile.is_open()) {
QString msg = "Unable to open CSM file [" + filePath + "].";
throw IException(IException::User, msg, _FILEINFO_);
}
std::stringstream buffer;
buffer << stateFile.rdbuf();
std::string content = buffer.str();

for (const csm::Plugin *plugin : csm::Plugin::getList())
for (size_t j = 0; j < plugin->getNumModels(); j++) {
std::string modelName = plugin->getModelName(j);
if (plugin->canModelBeConstructedFromState(modelName, content))
return plugin->constructModelFromState(content);
}

QString msg = "No loaded CSM plugin could construct a model from [" +
filePath + "].";
throw IException(IException::User, msg, _FILEINFO_);
}


/**
* Constructs CSM Model from ISD.
*
*
* @param isdFilePath Path to ISD file
* @param pluginName Plugin name
* @param modelName Model name
* @param isdFormat ISD format type (e.g., NITF)
*
*
*/
csm::Model *CameraFactory::constructModelFromIsd(QString isdFilePath, QString pluginName, QString modelName, QString isdFormat) {

Expand Down
4 changes: 4 additions & 0 deletions isis/src/base/objs/CameraFactory/CameraFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ namespace Isis {
static int CameraVersion(Cube &cube);
static int CameraVersion(Pvl &lab);
static void initPlugin();
static Camera *CreateFromIsd(const QString &isdFile, Cube &cube);
static void updateLabelForCsm(Cube &cube, csm::Model *model);
static void writeAdjustedCsmState(const QString &cubeFile, const QString &state, const QString &prefix = "");
static csm::Model *constructModelFromIsdOrState(const QString &filePath);
static csm::Model *constructModelFromIsd(QString isdFilePath, QString pluginName = "", QString modelName = "", QString isdFormat = "");
static QStringList getModelSpecFromIsd(QString isdFilePath, QString pluginName = "", QString modelName = "");

Expand Down
20 changes: 20 additions & 0 deletions isis/src/base/objs/SerialNumberList/SerialNumberList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ namespace Isis {
*/
void SerialNumberList::add(const QString &filename, bool def2filename) {
Pvl p(Isis::FileName(filename).expanded());
add(p, filename, def2filename);
}


/**
* Adds a new filename / serial number pair to the SerialNumberList using an
* existing Pvl label.
*
* @param p The Pvl label to be used
* @param filename The filename to be added
* @param def2filename If a serial number could not be found, try to return the filename
*
* @throws IException::User "Unable to find Instrument or Mapping group for comparing target."
* @throws IException::User "Unable to find Instrument group for comparing target."
* @throws IException::User "Target name from file does not match."
* @throws IException::User "Invalid serial number [Unknown] from file."
* @throws IException::User "Duplicate serial number from files [file1] and [file2]."
* @throws IException::User "FileName cannot be added to serial number list."
*/
void SerialNumberList::add(Pvl &p, const QString &filename, bool def2filename) {
PvlObject cubeObj = p.findObject("IsisCube");

try {
Expand Down
2 changes: 2 additions & 0 deletions isis/src/base/objs/SerialNumberList/SerialNumberList.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ find files of those names at the top level of this repository. **/
namespace Isis {

class Progress;
class Pvl;

/**
* @brief Serial Number list generator
Expand Down Expand Up @@ -68,6 +69,7 @@ namespace Isis {
virtual ~SerialNumberList();

void add(const QString &filename, bool def2filename = false);
void add(Pvl &p, const QString &filename, bool def2filename = false);
void add(const QString &serialNumber, const QString &filename);
void add(const char *serialNumber, const char *filename);
bool hasSerialNumber(QString sn);
Expand Down
Loading
Loading