From 4a13290e70bb57a8e8da7b73bb0bfe9a6b8978a6 Mon Sep 17 00:00:00 2001
From: Stuart Sides <ssides@usgs.gov>
Date: Sun, 12 Apr 2020 13:51:41 -0700
Subject: [PATCH] Add support for Mars Express HRSC SRC instrument (#3826)

* Add support for MacOS 10.14, needed for make base tests

* Add support for MEX SRC instrument

* Fixs found by PR review

* Fixed PR issues

* Tweaking translation

* Added exposure duration

* Adding in the SRC model

* Now with lat/lon, and test updates

* Adding Mex SRC Camera unitest

* Fix spelling in application docs.

Co-authored-by: Kristin <kberry@usgs.gov>
---
 .../serialnumbers/MexSrcSerialNumber.trn      |  42 +++
 isis/appdata/translations/Instruments.trn     |   1 +
 .../src/mex/apps/hrsc2isis/MexHrscArchive.trn |  25 +-
 .../src/mex/apps/hrsc2isis/MexHrscBandBin.trn |  27 +-
 .../mex/apps/hrsc2isis/MexHrscInstrument.trn  |  57 ++--
 .../src/mex/apps/hrsc2isis/MexHrscKernels.trn |  21 ++
 isis/src/mex/apps/hrsc2isis/hrsc2isis.xml     |  51 ++--
 isis/src/mex/apps/hrsc2isis/main.cpp          | 283 ++++++++++--------
 .../mex/apps/hrsc2isis/tsts/default/Makefile  |   2 +
 .../mex/apps/hrsc2isis/tsts/srcImage/Makefile |  15 +-
 .../mex/objs/MexHrscSrcCamera/Camera.plugin   |   6 +
 .../MexHrscSrcCamera/MexHrscSrcCamera.cpp     | 129 ++++++++
 .../objs/MexHrscSrcCamera/MexHrscSrcCamera.h  |  86 ++++++
 .../MexHrscSrcCamera/MexHrscSrcCamera.truth   |  34 +++
 .../mex/objs/MexHrscSrcCamera/unitTest.cpp    | 133 ++++++++
 15 files changed, 689 insertions(+), 223 deletions(-)
 create mode 100644 isis/appdata/serialnumbers/MexSrcSerialNumber.trn
 create mode 100644 isis/src/mex/apps/hrsc2isis/MexHrscKernels.trn
 create mode 100644 isis/src/mex/objs/MexHrscSrcCamera/Camera.plugin
 create mode 100644 isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.cpp
 create mode 100644 isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.h
 create mode 100644 isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.truth
 create mode 100644 isis/src/mex/objs/MexHrscSrcCamera/unitTest.cpp

diff --git a/isis/appdata/serialnumbers/MexSrcSerialNumber.trn b/isis/appdata/serialnumbers/MexSrcSerialNumber.trn
new file mode 100644
index 0000000000..712e82c239
--- /dev/null
+++ b/isis/appdata/serialnumbers/MexSrcSerialNumber.trn
@@ -0,0 +1,42 @@
+Group = Keyword1
+  Auto
+  InputKey       = SpacecraftName
+  InputGroup     = "IsisCube,Instrument"
+  InputPosition  = (IsisCube, Instrument)
+  OutputName     = Keyword1
+  OutputPosition = (Group, SerialNumberKeywords)
+  Translation    = (MEX, "MARS EXPRESS")
+  Translation    = (MEX, MARS_EXPRESS)
+  Translation    = (*, *)
+End_Group
+
+Group = Keyword2
+  Auto
+  InputKey       = SpacecraftClockStartCount
+  InputGroup     = "IsisCube,Instrument"
+  InputPosition  = (IsisCube, Instrument)
+  OutputName     = Keyword2
+  OutputPosition = (Group, SerialNumberKeywords)
+  Translation    = (*, *)
+End_Group
+
+Group = Keyword3
+  Auto
+  InputKey       = InstrumentId
+  InputGroup     = "IsisCube,Instrument"
+  InputPosition  = (IsisCube, Instrument)
+  OutputName     = Keyword3
+  OutputPosition = (Group, SerialNumberKeywords)
+  Translation    = (*, *)
+End_Group
+
+Group = Keyword4
+  Auto
+  InputKey       = DetectorId
+  InputGroup     = "IsisCube,Archive"
+  InputPosition  = (IsisCube, Archive)
+  OutputName     = Keyword4
+  OutputPosition = (Group, SerialNumberKeywords)
+  Translation    = (*, *)
+End_Group
+End
diff --git a/isis/appdata/translations/Instruments.trn b/isis/appdata/translations/Instruments.trn
index b45ca98bd1..4bf42fade9 100644
--- a/isis/appdata/translations/Instruments.trn
+++ b/isis/appdata/translations/Instruments.trn
@@ -108,6 +108,7 @@ Group = InstrumentName
 
   # MEX
   Translation   = (Hrsc, HRSC)
+  Translation   = (Src, SRC)
 
   # Messenger
   Translation   = (Mdis, MDIS-NAC)
diff --git a/isis/src/mex/apps/hrsc2isis/MexHrscArchive.trn b/isis/src/mex/apps/hrsc2isis/MexHrscArchive.trn
index 892406a68e..857bf5ff75 100644
--- a/isis/src/mex/apps/hrsc2isis/MexHrscArchive.trn
+++ b/isis/src/mex/apps/hrsc2isis/MexHrscArchive.trn
@@ -1,25 +1,6 @@
-#
-# The group names listed here are the native names used by
-# applications to get the foreign names and values
-#
-# There is only one level of groups the groups are not
-# allowed to be nested
-#
-# The Group names can NOT be repeated.
-#
-# InputGroup is a comma delimited list of objects and/or
-# groups in the foreign label. Traversing this list will
-# lead to the correct level to find the foreign keyword.
-#
-# InputKey is the keyword within the group which holds
-# the information.
-#
-# InputDefault is the value used if there is no value for
-# the keyword
-#
-# Translation is the native and corresponding foreign values.
-# Translation may be repeated as needed.
-#
+# Translations for HRSC Stereo and SRC instrument.
+# ISIS cube label Archive group
+
 Group = DataSetId
   Auto
   InputKey       = DATA_SET_ID
diff --git a/isis/src/mex/apps/hrsc2isis/MexHrscBandBin.trn b/isis/src/mex/apps/hrsc2isis/MexHrscBandBin.trn
index 2a9de3010c..f611c2532e 100644
--- a/isis/src/mex/apps/hrsc2isis/MexHrscBandBin.trn
+++ b/isis/src/mex/apps/hrsc2isis/MexHrscBandBin.trn
@@ -1,32 +1,12 @@
-#
-# The group names listed here are the native names used by
-# applications to get the foreign names and values
-#
-# There is only one level of groups the groups are not
-# allowed to be nested
-#
-# The Group names can NOT be repeated.
-#
-# InputGroup is a comma delimited list of objects and/or
-# groups in the foreign label. Traversing this list will
-# lead to the correct level to find the foreign keyword.
-#
-# InputKey is the keyword within the group which holds
-# the information.
-#
-# InputDefault is the value used if there is no value for
-# the keyword
-#
-# Translation is the native and corresponding foreign values.
-# Translation may be repeated as needed.
-#
+# Translations for HRSC Stereo and SRC instrument.
+# ISIS cube label BandBin group
+
 Group = FilterWidth
   Auto
   InputKey       = BANDWIDTH
   OutputName     = Width
   OutputPosition = (Group, BandBin)
   Translation    = (*, *)
-  Translation    = ("--- Illegal Value ---", *)
 End_Group
 
 Group = FilterCenter
@@ -35,6 +15,5 @@ Group = FilterCenter
   OutputName     = Center
   OutputPosition = (Group, BandBin)
   Translation    = (*, *)
-  Translation    = ("--- Illegal Value ---", *)
 End_Group
 End
diff --git a/isis/src/mex/apps/hrsc2isis/MexHrscInstrument.trn b/isis/src/mex/apps/hrsc2isis/MexHrscInstrument.trn
index 8f2633f3fd..038fc8850b 100644
--- a/isis/src/mex/apps/hrsc2isis/MexHrscInstrument.trn
+++ b/isis/src/mex/apps/hrsc2isis/MexHrscInstrument.trn
@@ -1,25 +1,6 @@
-#
-# The group names listed here are the native names used by
-# applications to get the foreign names and values
-#
-# There is only one level of groups the groups are not
-# allowed to be nested
-#
-# The Group names can NOT be repeated.
-#
-# InputGroup is a comma delimited list of objects and/or
-# groups in the foreign label. Traversing this list will
-# lead to the correct level to find the foreign keyword.
-#
-# InputKey is the keyword within the group which holds
-# the information.
-#
-# InputDefault is the value used if there is no value for
-# the keyword
-#
-# Translation is the native and corresponding foreign values.
-# Translation may be repeated as needed.
-#
+# Translations for HRSC Stereo and SRC instrument.
+# ISIS cube label Instrument group
+
 Group = SpacecraftName
   Auto
   InputKey       = INSTRUMENT_HOST_NAME
@@ -31,10 +12,20 @@ End_Group
 
 Group = InstrumentId
   Auto
-  InputKey       = INSTRUMENT_ID
+  InputKey       = DETECTOR_ID
   OutputName     = InstrumentId
   OutputPosition = (Group, Instrument)
-  Translation    = (*, *)
+  Translation    = (HRSC, "MEX_HRSC_HEAD")
+  Translation    = (HRSC, "MEX_HRSC_S2")
+  Translation    = (HRSC, "MEX_HRSC_RED")
+  Translation    = (HRSC, "MEX_HRSC_P2")
+  Translation    = (HRSC, "MEX_HRSC_BLUE")
+  Translation    = (HRSC, "MEX_HRSC_NADIR")
+  Translation    = (HRSC, "MEX_HRSC_GREEN")
+  Translation    = (HRSC, "MEX_HRSC_P1")
+  Translation    = (HRSC, "MEX_HRSC_IR")
+  Translation    = (HRSC, "MEX_HRSC_S1")
+  Translation    = (SRC, "MEX_HRSC_SRC")
 End_Group
 
 Group = StartTime
@@ -88,6 +79,24 @@ Group = TargetName
   Translation    = (Cal, CAL)
 End_Group
 
+Group = Summing
+  Auto
+  Optional
+  InputKey       = MACROPIXEL_SIZE
+  OutputName     = Summing
+  OutputPosition = (Group, Instrument)
+  Translation    = (*, *)
+End_Group
+
+Group = ExpousreDuration
+  Auto
+  Optional
+  InputKey       = EXPOSURE_DURATION
+  OutputName     = ExposureDuration
+  OutputPosition = (Group, Instrument)
+  Translation    = (*, *)
+End_Group
+
 Group = FocalPlaneTemperature
   Auto
   Optional
diff --git a/isis/src/mex/apps/hrsc2isis/MexHrscKernels.trn b/isis/src/mex/apps/hrsc2isis/MexHrscKernels.trn
new file mode 100644
index 0000000000..5b880398fe
--- /dev/null
+++ b/isis/src/mex/apps/hrsc2isis/MexHrscKernels.trn
@@ -0,0 +1,21 @@
+# Translations for HRSC Stereo and SRC instrument.
+# ISIS cube label Kernels group
+
+Group = NaifIkCode
+  Auto
+  InputKey       = DETECTOR_ID
+  OutputName     = NaifIkCode
+  OutputPosition = (Group, Kernels)
+  Translation    = (-41210, "MEX_HRSC_HEAD")
+  Translation    = (-41211, "MEX_HRSC_S2")
+  Translation    = (-41212, "MEX_HRSC_RED")
+  Translation    = (-41213, "MEX_HRSC_P2")
+  Translation    = (-41214, "MEX_HRSC_BLUE")
+  Translation    = (-41215, "MEX_HRSC_NADIR")
+  Translation    = (-41216, "MEX_HRSC_GREEN")
+  Translation    = (-41217, "MEX_HRSC_P1")
+  Translation    = (-41218, "MEX_HRSC_IR")
+  Translation    = (-41219, "MEX_HRSC_S1")
+  Translation    = (-41220, "MEX_HRSC_SRC")
+End_Group
+
diff --git a/isis/src/mex/apps/hrsc2isis/hrsc2isis.xml b/isis/src/mex/apps/hrsc2isis/hrsc2isis.xml
index dad2d188be..83d8227b15 100644
--- a/isis/src/mex/apps/hrsc2isis/hrsc2isis.xml
+++ b/isis/src/mex/apps/hrsc2isis/hrsc2isis.xml
@@ -5,18 +5,26 @@
   <brief>Import HRSC image</brief>
 
   <description> 
-    This program imports Mars Express HRSC files. This works by first
-    determining whether or not the input file has prefix data. If there is
-    prefix data, the prefix data is collected and used to look for "gaps" - HRSC
-    files can give us a time and exposure duration  for each line, we look for 
-    where the time + exposure duration =/= next line's time.   We populate a 
-    table (LineScanTimes) with the prefix data and lineInFile with whether or 
-    not a gap should be inserted. For all files, we now process the  data and if 
-    there were gaps, they will be put in their proper places. This is a two-pass
-    system for files with prefix data, one-pass for files without.  The Isis2
-    equivalent to this program is mex2isis.pl. It is worth noting that
-    regardless of the  input file's byte order, the prefix data byte order is 
-    always LSB.
+    <p>
+      This program imports Mars Express HRSC files. This includes both the HRSC 
+      Stereo and the SRC instruments with procesing level of "1" or "2" (not "3"). 
+      The program populates all label keywords necessary to create the corresponding camera
+      models.
+    </p>
+    <p>
+      For the stereo instrument there is always prefix data. The
+      prefix data is collected and used to look for "gaps" aka missing lines. The 
+      line prefix data for HRSC
+      files contain time and exposure duration for each line. We look for occurrences  
+      where the time + exposure duration is not equal to the next line's time to find missing
+      lines. This program uses two passes, the first reads the PDS file into a cube, the
+      second inserts lines where gaps were identified.
+    </p>
+    <p>
+      For the SRC instrument, this program reads the PDS file and converts it to an ISIS cube. The
+      PDS file for the SRC instrument is much simpler than the stereo instrument files and requires
+      no special processing. 
+    </p>
   </description>
 
   <history>
@@ -32,14 +40,17 @@
     </change>
     <change name="Steven Lambright" date="2008-07-28">
       Updated to support the HRSC camera model. The Kernels group now has the IK code
-        properly added, the summing keyword is propagated, the times table is now compressed
-        into only having entries on time changes, and the times no longer have a 'Z' at the end
-        of them once imported.
+      properly added, the summing keyword is propagated, the times table is now compressed
+      into only having entries on time changes, and the times no longer have a 'Z' at the end
+      of them once imported.
     </change>
     <change name="Adam Paquette" date="2016-10-05">
       Updated the error messages thrown when hrsc2isis trys to ingest an image that is not an HRSC
       image, and when hrsc2isis trys to ingest a map projected image. Fixes #4259.
     </change>
+    <change name="Stuart Sides" date="2020-03-31">
+      Added ability to read HRSC SRC images
+    </change>
   </history>
 
   <oldName>
@@ -57,7 +68,7 @@
         <type>filename</type>
         <fileMode>input</fileMode>
         <brief>
-          Input HRSC Image
+          Input HRSC Stereo or SRC Image
         </brief>
         <description>
           Use this parameter to select the PDS filename.  This file
@@ -68,7 +79,7 @@
           binary image data.
         </description>
         <filter>
-          *.lbl *.img
+          *.lbl *.LBL *.img *.IMG
         </filter>
       </parameter>
 
@@ -90,10 +101,10 @@
 
   <examples>
     <example>
-      <brief>Import an HRSC image with gaps</brief>
+      <brief>Import an HRSC stereo instrument image with gaps</brief>
       <description>
-        This example covers importing an HRSC image and shows what happens when 
-        gaps are found.
+        This example covers importing an HRSC stereo instrument image and shows 
+        what happens when gaps are found.
       </description>
       <terminalInterface>
         <commandLine> from=h0279_0000_re2.img to=h0279_0000_re2.cub</commandLine>
diff --git a/isis/src/mex/apps/hrsc2isis/main.cpp b/isis/src/mex/apps/hrsc2isis/main.cpp
index bff78f365e..573a177f86 100644
--- a/isis/src/mex/apps/hrsc2isis/main.cpp
+++ b/isis/src/mex/apps/hrsc2isis/main.cpp
@@ -11,93 +11,146 @@
 using namespace std;
 using namespace Isis;
 
-void IgnoreData(Isis::Buffer &buf) {};
+void ImportHrscStereoImage(ProcessImportPds &p, Pvl &label);
+void ImportHrscSrcImage(ProcessImportPds &p, Pvl &label);
+
+void IgnoreData(Isis::Buffer &buf);
 void WriteOutput(Isis::Buffer &buf);
+
 void TranslateHrscLabels(Pvl &inLabels, Pvl &outLabel);
 
 Cube *outCube = NULL;
 long numLinesSkipped = 0;
 std::vector< bool > lineInFile;
 
-/**
- * This program imports Mars Express HRSC files
- *
- *  This works by first determining whether or not the input file
- *    has prefix data.
- *
- *  If there is prefix data, a StartProcess is called with the
- *    IgnoreData() function callback in order to get the import class to
- *    collect prefix data. Once the prefix data is populated, we go ahead
- *    and look for "gaps" - HRSC files can give us a time and exposure duration
- *    for each line, we look for where the time + exposure duration != next line's time.
- *    We populate a table (LineScanTimes) with the prefix data and lineInFile with whether or not
- *    a gap should be inserted.
- *
- *  For all files, we now process the data using the WriteOutput callback. If there were gaps,
- *    WriteOutput will put them in their proper places. Finally, we translate the labels and put
- *    the LineScanTimes (if necessary) in the output cube.
- *
- *   This is a two-pass system for files with prefix data, one-pass for files without.
- *
- *   The Isis2 equivalent to this program is mex2isis.pl. It is worth noting that regardless of the
- *     input file's byte order, the prefix data byte order is always LSB.
- */
+
 void IsisMain() {
   UserInterface &ui = Application::GetUserInterface();
 
-  try {
-    Pvl temp(ui.GetFileName("FROM"));
-    // Check for HRSC file
-    if(temp["INSTRUMENT_ID"][0] != "HRSC") throw IException();
+  ProcessImportPds p;
+  Pvl label;
+  p.SetPdsFile(ui.GetFileName("FROM"), "", label);
+
+  // Decide if the file is an HRSC image or something else
+  if (label["INSTRUMENT_ID"][0] != "HRSC") {
+    IString msg = "File [" + ui.GetFileName("FROM") + "] with [INSTRUMENT_ID = " +
+                   label["INSTRUMENT_ID"][0] +  
+                  "] does not appear to be a Mars Express HRSC image. " +
+                  "Consider using pds2isis to import the image.";
+    throw IException(IException::User, msg, _FILEINFO_);
+  }
+
+  // Decide if the file is an HRSC SRC, HRSC Stereo (S2) or something else
+  bool isSrcFile;
+  if (label["DETECTOR_ID"][0] == "MEX_HRSC_SRC") {
+    isSrcFile = true;
+  }
+  else if ((label["DETECTOR_ID"][0] == "MEX_HRSC_S2") || 
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_RED") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_P2") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_BLUE") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_NADIR") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_GREEN") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_P1") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_IR") ||
+           (label["DETECTOR_ID"][0] == "MEX_HRSC_S1")) {
+    isSrcFile = false;
   }
-  catch(IException &e) {
-    IString msg = "File [" + ui.GetFileName("FROM") +
-                  "] does not appear to be a Mars Express HRSC image.";
+  else {
+    QString msg = "File [" + ui.GetFileName("FROM");
+    msg += "] does not appear to be a Mars Express stereo or SRC file. Label keyword [DETECTOR_ID = ";
+    msg += label["DETECTOR_ID"][0] + "] is not recognized.";
     throw IException(IException::User, msg, _FILEINFO_);
   }
 
-  ProcessImportPds p;
-  Pvl label;
-  lineInFile.clear();
-  numLinesSkipped = 0;
+  // This program is setup to work with Mex HRSC processing level 1 and 2 only.
+  // Not level 3 (Mapped)
+  if ((int)label["PROCESSING_LEVEL_ID"] >= 3) {
+    QString msg = "File [" + ui.GetFileName("FROM");
+    msg += "] has keyword [PROCESSING_LEVEL_ID = " + label["PROCESSING_LEVEL_ID"][0] + "]";
+    msg += " and can not be read by this program.";
+    throw IException(IException::User, msg, _FILEINFO_);
+  }
 
-  p.SetPdsFile(ui.GetFileName("FROM"), "", label);
+  // The processing for Stereo and SRC are significantly different. Call the
+  // appropriate processing function
+  if (isSrcFile) {
+    ImportHrscSrcImage(p, label);
+  }
+  else {
+    ImportHrscStereoImage(p, label);
+  }
 
-  CubeAttributeOutput outAtt(ui.GetFileName("TO"));
-  outCube = new Cube();
 
-  outCube->setByteOrder(outAtt.byteOrder());
-  outCube->setFormat(outAtt.fileFormat());
-  outCube->setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+}
 
-  /**
-    * Isis2 mex2isis.pl:
-    *   if (index($detector_id,"MEX_HRSC_SRC") < 0 &&
-    *   $processing_level_id < 3)
-    */
 
-  bool isSrcFile = (label["DETECTOR_ID"][0] != "MEX_HRSC_SRC");
-  bool mapProjRdr = ((int)label["PROCESSING_LEVEL_ID"] >= 3);
+// Import a PDS3, HRSC, SRC Camera image.
+void ImportHrscSrcImage(ProcessImportPds &p, Pvl &label) {
 
-  try {
-    if (!isSrcFile) {
-      QString msg = "File [" + ui.GetFileName("FROM");
-      msg += "] is SRC data and cannot be read.";
-      throw IException(IException::User, msg, _FILEINFO_);
-    }
+  outCube = p.SetOutputCube("TO");
+  p.StartProcess();
 
-    if (mapProjRdr) {
-      QString msg = "File [" + ui.GetFileName("FROM");
-      msg += "] is map projected and cannot be read.";
-      throw IException(IException::User, msg, _FILEINFO_);
-    }
+  Pvl otherLabels;
+  TranslateHrscLabels(label, otherLabels);
+
+  if (otherLabels.hasGroup("Instrument") &&
+      (otherLabels.findGroup("Instrument").keywords() > 0)) {
+    outCube->putGroup(otherLabels.findGroup("Instrument"));
   }
 
-  catch (IException &e) {
-      QString msg = "File cannot be read by hrsc2isis, use pds2isis.";
-      throw IException(e, IException::User, msg, _FILEINFO_);
+  if (otherLabels.hasGroup("BandBin") &&
+      (otherLabels.findGroup("BandBin").keywords() > 0)) {
+    outCube->putGroup(otherLabels.findGroup("BandBin"));
   }
 
+  if (otherLabels.hasGroup("Archive") &&
+      (otherLabels.findGroup("Archive").keywords() > 0)) {
+    outCube->putGroup(otherLabels.findGroup("Archive"));
+  }
+
+  if (otherLabels.hasGroup("Kernels") &&
+      (otherLabels.findGroup("Kernels").keywords() > 0)) {
+    outCube->putGroup(otherLabels.findGroup("Kernels"));
+  }
+
+  p.EndProcess();
+}
+
+
+/**
+ * This function reads Mars Express HRSC Stereo files
+ *
+ * First pass through the file is a Process called with the IgnoreData() function in order to
+ * get the import class to collect prefix data. Once the prefix data is populated, we look for
+ * "gaps" - HRSC files with prefix data give us a time and exposure duration for each line, we
+ * look for where the time + exposure duration != next line's time. We populate a table
+ * (LineScanTimes) with the prefix data and lineInFile with whether or not a missing line should
+ * be inserted.
+ *                                                                      
+ * Second pass through the file is a Process called with the WriteOutput function. If there were
+ * gaps, WriteOutput will add lines at the appropriate positions in the output cube. Finally, we
+ * translate the labels and put the LineScanTimes (if necessary) in the output cube.
+ *
+ * This is a two-pass system for files with prefix data, one-pass for files without.
+ *  
+ * NOTE: Regardless of the input file's byte order IMAGE-SAMPLE_TYPE, the prefix data byte order
+ * is always LSB.
+ */
+void ImportHrscStereoImage(ProcessImportPds &p, Pvl &label) {
+
+  UserInterface &ui = Application::GetUserInterface();
+
+  lineInFile.clear();
+  numLinesSkipped = 0;
+
+  CubeAttributeOutput outAtt(ui.GetFileName("TO"));
+  outCube = new Cube();
+
+  outCube->setByteOrder(outAtt.byteOrder());
+  outCube->setFormat(outAtt.fileFormat());
+  outCube->setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+
   TableField ephTimeField("EphemerisTime", TableField::Double);
   TableField expTimeField("ExposureTime", TableField::Double);
   TableField lineStartField("LineStart", TableField::Integer);
@@ -115,6 +168,7 @@ void IsisMain() {
   p.Progress()->SetText("Reading Prefix Data");
   p.StartProcess(IgnoreData);
 
+  // Get the prefix data from the Process
   // The prefix data is always in LSB format, regardless of the overall file format
   EndianSwapper swapper("LSB");
 
@@ -122,23 +176,23 @@ void IsisMain() {
   std::vector<double> exposureTimes;
   std::vector< std::vector<char *> > prefix = p.DataPrefix();
 
-  for(int line = 0; line < p.Lines(); line++) {
+  for (int line = 0; line < p.Lines(); line++) {
     double ephTime = swapper.Double((double *)prefix[0][line]);
     double expTime = swapper.Float((float *)(prefix[0][line] + 8)) / 1000.0;
 
-    if(line > 0) {
+    if (line > 0) {
       /**
        * We know how many skipped lines with this equation. We take the
-       *   difference in the current line and the last line's time, which will
-       *   ideally be equal to the last line's exposure duration. We divide this by
-       *   the last line's exposure duration, and the result is the 1-based count of
-       *   how many exposures there were between the last line and the current line.
-       *   We subtract one in order to remove the known exposure, and the remaining should
-       *   be the 1-based count of how many lines were skipped. Add 0.5 to round up.
+       * difference in the current line and the last line's time, which will
+       * ideally be equal to the last line's exposure duration. We divide this by
+       * the last line's exposure duration, and the result is the 1-based count of
+       * how many exposures there were between the last line and the current line.
+       * We subtract one in order to remove the known exposure, and the remaining should
+       * be the 1-based count of how many lines were skipped. Add 0.5 to round up.
        */
       int skippedLines = (int)((ephTime - ephemerisTimes.back()) / exposureTimes.back() - 1.0 + 0.5);
 
-      for(int i = 0; i < skippedLines; i++) {
+      for (int i = 0; i < skippedLines; i++) {
         ephemerisTimes.push_back(ephemerisTimes.back() + exposureTimes.back());
         exposureTimes.push_back(exposureTimes.back());
         lineInFile.push_back(false);
@@ -151,8 +205,8 @@ void IsisMain() {
   }
 
   double lastExp = 0.0;
-  for(unsigned int i = 0; i < ephemerisTimes.size(); i++) {
-    if(lastExp != exposureTimes[i]) {
+  for (unsigned int i = 0; i < ephemerisTimes.size(); i++) {
+    if (lastExp != exposureTimes[i]) {
       lastExp = exposureTimes[i];
       timesRecord[0] = ephemerisTimes[i];
       timesRecord[1] = exposureTimes[i];
@@ -169,33 +223,27 @@ void IsisMain() {
 
   outCube->write(timesTable);
 
-  // Get as many of the other labels as we can
+  // Translate the PDS labels into ISIS labels and add them to the cube
   Pvl otherLabels;
 
-  //p.TranslatePdsLabels (otherLabels);
   TranslateHrscLabels(label, otherLabels);
 
-  if(otherLabels.hasGroup("Mapping") &&
-      (otherLabels.findGroup("Mapping").keywords() > 0)) {
-    outCube->putGroup(otherLabels.findGroup("Mapping"));
-  }
-
-  if(otherLabels.hasGroup("Instrument") &&
+  if (otherLabels.hasGroup("Instrument") &&
       (otherLabels.findGroup("Instrument").keywords() > 0)) {
     outCube->putGroup(otherLabels.findGroup("Instrument"));
   }
 
-  if(otherLabels.hasGroup("BandBin") &&
+  if (otherLabels.hasGroup("BandBin") &&
       (otherLabels.findGroup("BandBin").keywords() > 0)) {
     outCube->putGroup(otherLabels.findGroup("BandBin"));
   }
 
-  if(otherLabels.hasGroup("Archive") &&
+  if (otherLabels.hasGroup("Archive") &&
       (otherLabels.findGroup("Archive").keywords() > 0)) {
     outCube->putGroup(otherLabels.findGroup("Archive"));
   }
 
-  if(otherLabels.hasGroup("Kernels") &&
+  if (otherLabels.hasGroup("Kernels") &&
       (otherLabels.findGroup("Kernels").keywords() > 0)) {
     outCube->putGroup(otherLabels.findGroup("Kernels"));
   }
@@ -208,32 +256,49 @@ void IsisMain() {
   lineInFile.clear();
 }
 
+
+// Processing function called by ProcessImportPds:StartProcess for each HRSC Stereo instrumennt
+// line. It ignores the image data and returns. This is used to get the ProcessImportPds object
+// to collect the prefix bytes. 
+void IgnoreData(Isis::Buffer &buf) {
+  return;
+}
+
+
+// Processing function called by ProcessImportPds:StartProcess for each HRSC Stereo instrument
+// line in the IMG file.
 void WriteOutput(Isis::Buffer &buf) {
+
   LineManager outLines(*outCube);
 
-  if(lineInFile.size()) {
-    for(int i = 0; i < outLines.size(); i++) {
+  if (lineInFile.size()) {
+    for (int i = 0; i < outLines.size(); i++) {
       outLines[i] = Isis::Null;
     }
 
-    while(!lineInFile[(buf.Line()+numLinesSkipped) % lineInFile.size()]) {
+    while (!lineInFile[(buf.Line()+numLinesSkipped) % lineInFile.size()]) {
       outLines.SetLine(buf.Line() + numLinesSkipped, buf.Band());
       outCube->write(outLines);
-      numLinesSkipped ++;
+      numLinesSkipped++;
     }
   }
 
   outLines.SetLine(buf.Line() + numLinesSkipped, buf.Band());
 
-  // outLines.Copy(buf); doesn't work because the raw buffers don't match
-  for(int i = 0; i < outLines.size(); i++)
+  for (int i = 0; i < outLines.size(); i++) {
     outLines[i] = buf[i];
+  }
 
   outCube->write(outLines);
+
+  return;
 }
 
+
+// Translate HRSC Stereo lables into ISIS labels
 void TranslateHrscLabels(Pvl &inLabels, Pvl &outLabel) {
-  // Get the directory where the MRO HiRISE translation tables are.
+
+  // Get the directory where the translation tables are.
   QString transDir = "$ISISROOT/appdata/translations/";
 
   // Translate the Instrument group
@@ -241,13 +306,6 @@ void TranslateHrscLabels(Pvl &inLabels, Pvl &outLabel) {
   PvlToPvlTranslationManager instrumentXlater(inLabels, transFile.expanded());
   instrumentXlater.Auto(outLabel);
 
-  if(inLabels.hasKeyword("MACROPIXEL_SIZE")) {
-    outLabel.findGroup("Instrument", Pvl::Traverse) += PvlKeyword("Summing", inLabels["MACROPIXEL_SIZE"][0]);
-  }
-  else {
-    outLabel.findGroup("Instrument", Pvl::Traverse) += PvlKeyword("Summing", "1");
-  }
-
   // Remove 'Z' from times
   QString startTime = outLabel.findGroup("Instrument", Pvl::Traverse)["StartTime"][0];
   startTime = startTime.mid(0, startTime.size() - 1);
@@ -267,30 +325,9 @@ void TranslateHrscLabels(Pvl &inLabels, Pvl &outLabel) {
   PvlToPvlTranslationManager archiveXlater(inLabels, transFile.expanded());
   archiveXlater.Auto(outLabel);
 
-  std::map<QString, int> naifIkCodes;
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_HEAD", -41210));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_S2",   -41211));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_RED",  -41212));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_P2",   -41213));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_BLUE", -41214));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_NADIR", -41215));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_GREEN", -41216));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_P1",   -41217));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_IR",   -41218));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_S1",   -41219));
-  naifIkCodes.insert(std::pair<QString, int>("MEX_HRSC_SRC",  -41220));
-
-  QString key = outLabel.findGroup("Archive", Pvl::Traverse)["DetectorId"];
-  int ikCode = naifIkCodes[key];
-
-  if(ikCode < -41220 || ikCode > -41210) {
-    QString msg = "Unrecognized Detector ID [";
-    msg += key;
-    msg += "]";
-    throw IException(IException::Unknown, msg, _FILEINFO_);
-  }
+  // Translate the Kernels group
+  transFile  = transDir + "MexHrscKernels.trn";
+  PvlToPvlTranslationManager kernelsXlater(inLabels, transFile.expanded());
+  kernelsXlater.Auto(outLabel);
 
-  PvlGroup kerns("Kernels");
-  kerns += PvlKeyword("NaifIkCode", toString(ikCode));
-  outLabel.addGroup(kerns);
 }
diff --git a/isis/src/mex/apps/hrsc2isis/tsts/default/Makefile b/isis/src/mex/apps/hrsc2isis/tsts/default/Makefile
index 2c56d55b61..57b0e9b62d 100644
--- a/isis/src/mex/apps/hrsc2isis/tsts/default/Makefile
+++ b/isis/src/mex/apps/hrsc2isis/tsts/default/Makefile
@@ -6,6 +6,7 @@ commands:
 	$(APPNAME) FROM=$(INPUT)/h0279_0000_re2.img TO=$(OUTPUT)/h0279_0000_re2.cub > /dev/null;
 	tabledump FROM=$(OUTPUT)/h0279_0000_re2.cub TO=$(OUTPUT)/h0279_0000_re2.txt NAME=LineScanTimes > /dev/null;
 	catlab FROM=$(OUTPUT)/h0279_0000_re2.cub to=$(OUTPUT)/h0279_0000_re2.pvl > /dev/null;
+
 	if [ `$(APPNAME) FROM=$(INPUT)/h1580_0008_sr3.img \
 	  TO=$(OUTPUT)/junk.cub \
 	   >& $(OUTPUT)/error_message_temp.txt` ]; \
@@ -13,3 +14,4 @@ commands:
 	fi;
 	$(SED) 's+\[/.*/input/+\[input/+' $(OUTPUT)/error_message_temp.txt > $(OUTPUT)/error.txt;
 	$(RM)  $(OUTPUT)/error_message_temp.txt;
+
diff --git a/isis/src/mex/apps/hrsc2isis/tsts/srcImage/Makefile b/isis/src/mex/apps/hrsc2isis/tsts/srcImage/Makefile
index cddfcf70f5..b4b9cbdfae 100644
--- a/isis/src/mex/apps/hrsc2isis/tsts/srcImage/Makefile
+++ b/isis/src/mex/apps/hrsc2isis/tsts/srcImage/Makefile
@@ -2,13 +2,8 @@ APPNAME =hrsc2isis
 
 include $(ISISROOT)/make/isismake.tsts
 
-commands:#    TEST A: Check that a projected HRSC image fails to be read
-	echo -e "Test hrsc2isis with an image that has SRC data:" > $(OUTPUT)/error_message_temp.txt;
-	if [ `$(APPNAME) \
-	  FROM=$(INPUT)/h2862_0006_sr2.img \
-	  TO=$(OUTPUT)/temp.cub \
-	  2>> $(OUTPUT)/error_message_temp.txt > /dev/null` ]; \
-	  then true; \
-	fi;
-	$(SED) 's+\[/.*/input/+\[input/+' $(OUTPUT)/error_message_temp.txt > $(OUTPUT)/error_message.txt;
-	$(RM)  $(OUTPUT)/error_message_temp.txt;
+commands:
+	$(APPNAME) FROM=$(INPUT)/h2862_0006_sr2.img \
+	           TO=$(OUTPUT)/h2862_0006_sr2.cub \
+	           > /dev/null
+
diff --git a/isis/src/mex/objs/MexHrscSrcCamera/Camera.plugin b/isis/src/mex/objs/MexHrscSrcCamera/Camera.plugin
new file mode 100644
index 0000000000..495ae95857
--- /dev/null
+++ b/isis/src/mex/objs/MexHrscSrcCamera/Camera.plugin
@@ -0,0 +1,6 @@
+Group = MarsExpress/Src
+  Version = 1
+  Library = MexHrscSrcCamera
+  Routine = MexHrscSrcCameraPlugin
+EndGroup
+
diff --git a/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.cpp b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.cpp
new file mode 100644
index 0000000000..dbd8d86ec6
--- /dev/null
+++ b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.cpp
@@ -0,0 +1,129 @@
+/**
+ * @file
+ *
+ *   Unless noted otherwise, the portions of Isis written by the USGS are public
+ *   domain. See individual third-party library and package descriptions for
+ *   intellectual property information,user agreements, and related information.
+ *
+ *   Although Isis has been used by the USGS, no warranty, expressed or implied,
+ *   is made by the USGS as to the accuracy and functioning of such software
+ *   and related material nor shall the fact of distribution constitute any such
+ *   warranty, and no responsibility is assumed by the USGS in connection
+ *   therewith.
+ *
+ *   For additional information, launch
+ *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html in a browser or see
+ *   the Privacy &amp; Disclaimers page on the Isis website,
+ *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
+ *   http://www.usgs.gov/privacy.html.
+ */
+
+#include "MexHrscSrcCamera.h"
+
+#include <QString>
+
+#include "CameraDetectorMap.h"
+#include "CameraDistortionMap.h"
+#include "CameraFocalPlaneMap.h"
+#include "CameraGroundMap.h"
+#include "CameraSkyMap.h"
+#include "IString.h"
+#include "iTime.h"
+#include "NaifStatus.h"
+
+using namespace std;
+
+namespace Isis {
+  /**
+   * Constructs an Mex HRSC SRC Framing Camera object.
+   *
+   * @param lab Pvl label from a Mex HRSC SRC Framing Camera image.
+   *
+   * @author Stuart Sides 
+   *  
+   * @internal
+   */
+
+  MexHrscSrcCamera::MexHrscSrcCamera(Cube &cube) : FramingCamera(cube) {
+    m_instrumentNameLong = "Super Resolution Channel";
+    m_instrumentNameShort = "SRC";
+    m_spacecraftNameLong = "Mars Express";
+    m_spacecraftNameShort = "MEX";
+    
+    NaifStatus::CheckErrors();
+
+    SetFocalLength(Spice::getDouble("INS" + toString(naifIkCode()) + "_FOCAL_LENGTH"));
+
+    // For setting the pixel pitch, the Naif keyword PIXEL_SIZE is used instead of the ISIS
+    // default of PIXEL_PITCH, so set the value directly.
+    QString pp = "INS" + toString(naifIkCode()) + "_PIXEL_SIZE";
+    double pixelPitch = Spice::getDouble(pp);
+    pixelPitch /= 1000.0;
+    SetPixelPitch(pixelPitch);
+
+    // SRC doesn't appear to use any summing modes
+    CameraDetectorMap *detectorMap = new CameraDetectorMap(this);
+    detectorMap->SetDetectorSampleSumming(1);
+    detectorMap->SetDetectorLineSumming(1);
+
+    // Setup focal plane map. The class will read data from the instrument addendum kernel to pull
+    // out the affine transforms from detector samp,line to focal plane x,y.
+    CameraFocalPlaneMap *focalMap = new CameraFocalPlaneMap(this, naifIkCode());
+
+    // The boresight position recorded in the IK is zero-based and therefore needs to be adjusted 
+    // for ISIS
+    double boresightSample = Spice::getDouble("INS" + toString(naifIkCode()) + "_CCD_CENTER",0) + 1.0;
+    double boresightLine = Spice::getDouble("INS" + toString(naifIkCode()) + "_CCD_CENTER",1) + 1.0;
+    focalMap->SetDetectorOrigin(boresightSample,boresightLine);
+
+    // The distortion is documented as near 1 pixel at the corners. This is less than the
+    // point spread, so zero distortion is used
+    new CameraDistortionMap(this);
+
+    // Setup the ground and sky map
+    new CameraGroundMap(this);
+    new CameraSkyMap(this);
+
+    // The observation start time and clock count for SRC are based on the center of the exposure.
+    Pvl &lab = *cube.label();
+    PvlGroup &inst = lab.findGroup("Instrument", Pvl::Traverse);
+    QString clockCount = inst["SpacecraftClockStartCount"];
+    double et = getClockTime(clockCount).Et();
+    double exposureDuration = (double)inst["ExposureDuration"] / 1000.0;
+
+    pair<iTime, iTime> startStop = ShutterOpenCloseTimes(et, exposureDuration);
+    setTime(et);
+
+    // Internalize all the NAIF SPICE information into memory.
+    LoadCache();
+    NaifStatus::CheckErrors();
+  }
+
+  /**
+   * Returns the shutter open and close times.
+   * 
+   * @param time The SpacecraftClockStartCount converted to ephemeris time.
+   * @param exposureDuration ExposureDuration keyword value from the labels, converted to
+   *                         seconds.
+   *
+   * @return @b pair < @b iTime, @b iTime > The first value is the shutter
+   *         open time and the second is the shutter close time.
+   *
+   */
+  pair<iTime, iTime> MexHrscSrcCamera::ShutterOpenCloseTimes(double time,
+                                                             double exposureDuration) {
+    return FramingCamera::ShutterOpenCloseTimes(time - exposureDuration / 2.0, exposureDuration);
+  }
+}
+
+/**
+ * This is the function that is called in order to instantiate a MexHrscSrcCamera
+ * object.
+ *
+ * @param lab Cube labels
+ *
+ * @return Isis::Camera* MexHrscSrcCamera
+ */
+extern "C" Isis::Camera *MexHrscSrcCameraPlugin(Isis::Cube &cube) {
+  return new Isis::MexHrscSrcCamera(cube);
+}
diff --git a/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.h b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.h
new file mode 100644
index 0000000000..ea62aa707c
--- /dev/null
+++ b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.h
@@ -0,0 +1,86 @@
+#ifndef MexHrscSrcCamera_h
+#define MexHrscSrcCamera_h
+/**
+ * @file
+ *
+ *   Unless noted otherwise, the portions of Isis written by the USGS are public
+ *   domain. See individual third-party library and package descriptions for
+ *   intellectual property information,user agreements, and related information.
+ *
+ *   Although Isis has been used by the USGS, no warranty, expressed or implied,
+ *   is made by the USGS as to the accuracy and functioning of such software
+ *   and related material nor shall the fact of distribution constitute any such
+ *   warranty, and no responsibility is assumed by the USGS in connection
+ *   therewith.
+ *
+ *   For additional information, launch
+ *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html in a browser or see
+ *   the Privacy &amp; Disclaimers page on the Isis website,
+ *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
+ *   http://www.usgs.gov/privacy.html.
+ */
+
+#include "FramingCamera.h"
+
+#include <QString>
+
+namespace Isis {
+  /**
+   * This is the camera model for the Mex HRSC SRC Framing Camera 
+   *
+   * @ingroup SpiceInstrumentsAndCameras
+   * @ingroup Mex
+   *
+   * @author 2020-04-02 Stuart Sides
+   *
+   * @internal 
+   *   @history 2020-04-01 Stuart Sides - Initial version
+
+   */
+  class MexHrscSrcCamera : public FramingCamera {
+    public:
+      //! Create a MexHrscSrcCamera object
+      MexHrscSrcCamera(Cube &cube);
+
+      //! Destroys the MexHrscSrcCamera object
+      ~MexHrscSrcCamera() {};
+
+     /** 
+      * Reimplemented from FrameCamera 
+      *  
+      * @param time Start time of the observation
+      * @param exposureDuration The exposure duration of the observation
+      * 
+      * @return std::pair<iTime,iTime> The start and end times of the observation
+      */
+      virtual std::pair <iTime, iTime> ShutterOpenCloseTimes(double time,
+                                                             double exposureDuration);
+
+      /**
+       * 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 (-41001); }
+
+
+      /** 
+       * 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 Reference ID - J2000
+       * 
+       * @return @b int The appropriate instrument code for the Spacecraft
+       *         Kernel Reference ID
+       */
+      virtual int SpkReferenceId() const { return (1); }
+  };
+};
+#endif
diff --git a/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.truth b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.truth
new file mode 100644
index 0000000000..2d5da1ad26
--- /dev/null
+++ b/isis/src/mex/objs/MexHrscSrcCamera/MexHrscSrcCamera.truth
@@ -0,0 +1,34 @@
+Unit Test for MexHrscSrcCamera...
+FileName: H0010_0023_SR2.cub
+CK Frame: -41220
+
+Kernel IDs: 
+CK Frame ID = -41001
+CK Reference ID = 1
+SPK Target ID = -41
+SPK Reference ID = 1
+
+Spacecraft Name Long: Mars Express
+Spacecraft Name Short: MEX
+Instrument Name Long: Super Resolution Channel
+Instrument Name Short: SRC
+
+For upper left corner ...
+DeltaSample = 0.000000000
+DeltaLine = 0.000000000
+
+For upper right corner ...
+DeltaSample = 0.000000000
+DeltaLine = 0.000000000
+
+For lower left corner ...
+DeltaSample = 0.000000000
+DeltaLine = 0.000000000
+
+For lower right corner ...
+DeltaSample = 0.000000000
+DeltaLine = 0.000000000
+
+For center pixel position ...
+Latitude OK
+Longitude OK
diff --git a/isis/src/mex/objs/MexHrscSrcCamera/unitTest.cpp b/isis/src/mex/objs/MexHrscSrcCamera/unitTest.cpp
new file mode 100644
index 0000000000..0c1f27eebc
--- /dev/null
+++ b/isis/src/mex/objs/MexHrscSrcCamera/unitTest.cpp
@@ -0,0 +1,133 @@
+/**
+ * @file
+ *
+ *   Unless noted otherwise, the portions of Isis written by the USGS are public
+ *   domain. See individual third-party library and package descriptions for 
+ *   intellectual property information,user agreements, and related information.
+ *
+ *   Although Isis has been used by the USGS, no warranty, expressed or implied,
+ *   is made by the USGS as to the accuracy and functioning of such software 
+ *   and related material nor shall the fact of distribution constitute any such 
+ *   warranty, and no responsibility is assumed by the USGS in connection 
+ *   therewith.
+ *
+ *   For additional information, launch
+ *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html in a browser or see 
+ *   the Privacy &amp; Disclaimers page on the Isis website,
+ *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
+ *   http://www.usgs.gov/privacy.html.
+ */
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+
+#include "Camera.h"
+#include "CameraFactory.h"
+#include "FileName.h"
+#include "IException.h"
+#include "Preference.h"
+#include "Pvl.h"
+
+using namespace std;
+using namespace Isis;
+
+void TestLineSamp(Camera *cam, double samp, double line);
+/**
+ * @internal
+ *  
+ *   @history 2020-04-09 Stuart Sides - Original version.
+ */
+int main(void) {
+  Preference::Preferences(true);
+
+  cout << "Unit Test for MexHrscSrcCamera..." << endl;
+
+  try {
+    // These should be lat/lon at center of image. To obtain these numbers for a new cube/camera,
+    // set both the known lat and known lon to zero and copy the unit test output "Latitude off by: "
+    // and "Longitude off by: " values directly into these variables.
+    double knownLat = -9.3335948038633116;
+    double knownLon = 90.4734324741402531;
+
+    Cube c("$mex/testData/H0010_0023_SR2.cub", "r");
+    Camera *cam = CameraFactory::Create(c);
+    cout << "FileName: " << FileName(c.fileName()).name() << endl;
+    cout << "CK Frame: " << cam->instrumentRotation()->Frame() << endl << endl;
+    cout.setf(std::ios::fixed);
+    cout << setprecision(9);
+
+    // Test kernel IDs
+    cout << "Kernel IDs: " << endl;
+    cout << "CK Frame ID = " << cam->CkFrameId() << endl;
+    cout << "CK Reference ID = " << cam->CkReferenceId() << endl;
+    cout << "SPK Target ID = " << cam->SpkTargetId() << endl;
+    cout << "SPK Reference ID = " << cam->SpkReferenceId() << endl << endl;
+    
+    // Test name methods
+    cout << "Spacecraft Name Long: " << cam->spacecraftNameLong() << endl;
+    cout << "Spacecraft Name Short: " << cam->spacecraftNameShort() << endl;
+    cout << "Instrument Name Long: " << cam->instrumentNameLong() << endl;
+    cout << "Instrument Name Short: " << cam->instrumentNameShort() << endl << endl;
+
+    // Test all four corners to make sure the conversions are right
+    cout << "For upper left corner ..." << endl;
+    TestLineSamp(cam, 1.0, 1.0);
+
+    cout << "For upper right corner ..." << endl;
+    TestLineSamp(cam, cam->Samples(), 1.0);
+
+    cout << "For lower left corner ..." << endl;
+    TestLineSamp(cam, 1.0, cam->Lines());
+
+    cout << "For lower right corner ..." << endl;
+    TestLineSamp(cam, cam->Samples(), cam->Lines());
+
+    double samp = cam->Samples() / 2.0;
+    double line = cam->Lines() / 2.0;
+    cout << "For center pixel position ..." << endl;
+
+    if (!cam->SetImage(samp, line)) {
+      cout << "ERROR" << endl;
+      return 0;
+    }
+
+    if (fabs(cam->UniversalLatitude() - knownLat) < 1.81E-5) {
+      cout << "Latitude OK" << endl;
+    }
+    else {
+      cout << setprecision(16) << "Latitude off by: " << cam->UniversalLatitude() - knownLat << endl;
+    }
+
+    if (fabs(cam->UniversalLongitude() - knownLon) < 1.4E-6) {
+      cout << "Longitude OK" << endl;
+    }
+    else {
+      cout << setprecision(16) << "Longitude off by: " << cam->UniversalLongitude() - knownLon << endl;
+    }
+  }
+  catch (IException &e) {
+    e.print();
+  }
+}
+
+void TestLineSamp(Camera *cam, double samp, double line) {
+  bool success = cam->SetImage(samp, line);
+
+  if (success) {
+    success = cam->SetUniversalGround(cam->UniversalLatitude(), cam->UniversalLongitude());
+  }
+
+  if (success) {
+    double deltaSamp = samp - cam->Sample();
+    double deltaLine = line - cam->Line();
+    if (fabs(deltaSamp) < 0.008) deltaSamp = 0.0;
+    if (fabs(deltaLine) < 0.008) deltaLine = 0.0;
+    cout << "DeltaSample = " << deltaSamp << endl;
+    cout << "DeltaLine = " << deltaLine << endl << endl;
+  }
+  else {
+    cout << "DeltaSample = ERROR" << endl;
+    cout << "DeltaLine = ERROR" << endl << endl;
+  }
+}
+
-- 
GitLab