From cae8ec2d10a1f9ce3836e499e6a2b87d6a036e10 Mon Sep 17 00:00:00 2001
From: Cordell Michaud
Date: Wed, 25 May 2022 15:38:54 -0700
Subject: [PATCH] Re-add and refactor lrowacphomap (#4961)
* Add radiance units label in lrowaccal output cube
* Revert "Add radiance units label in lrowaccal output cube"
* Add refactored lrowacphomap app
* Add lrowacphomap functional tests and test data
* Add entry to CHANGELOG.md for the re-addition and refactoring of lrowacphomap
* Change ShapeModel keyword in Kernels group of lrowacphomap test cube label to relative path
* Add new test cube corresponding to the lrowacphomap test input cube's updated ShapeModel value
* Update expected stats result values for the LrowacphomapNoBack functional test
Co-authored-by: Victor Silva
---
CHANGELOG.md | 1 +
isis/src/lro/apps/lrowacphomap/HapkeLRO.cpp | 352 ++
isis/src/lro/apps/lrowacphomap/HapkeLRO.h | 123 +
isis/src/lro/apps/lrowacphomap/HapkeLROC.cpp | 638 +++
isis/src/lro/apps/lrowacphomap/HapkeLROC.h | 131 +
isis/src/lro/apps/lrowacphomap/Makefile | 7 +
.../lro/apps/lrowacphomap/lrowacphomap.cpp | 263 ++
isis/src/lro/apps/lrowacphomap/lrowacphomap.h | 14 +
.../lro/apps/lrowacphomap/lrowacphomap.xml | 483 +++
isis/src/lro/apps/lrowacphomap/main.cpp | 21 +
isis/tests/FunctionalTestsLrowacphomap.cpp | 98 +
...s_albflt_grid_geirist_tcorrect.reduced.cub | Bin 0 -> 96269 bytes
.../M1181493219CE.vis.odd.reduced.cub | 3403 +++++++++++++++
...LOBAL_with_LOLA_30M_POLE.16bit.reduced.cub | Bin 0 -> 803630 bytes
isis/tests/data/lrowacphomap/back.reduced.cub | 3729 +++++++++++++++++
.../lrowacphomap/hapke_full_reformatted.pvl | 46 +
16 files changed, 9309 insertions(+)
create mode 100644 isis/src/lro/apps/lrowacphomap/HapkeLRO.cpp
create mode 100644 isis/src/lro/apps/lrowacphomap/HapkeLRO.h
create mode 100644 isis/src/lro/apps/lrowacphomap/HapkeLROC.cpp
create mode 100644 isis/src/lro/apps/lrowacphomap/HapkeLROC.h
create mode 100644 isis/src/lro/apps/lrowacphomap/Makefile
create mode 100644 isis/src/lro/apps/lrowacphomap/lrowacphomap.cpp
create mode 100644 isis/src/lro/apps/lrowacphomap/lrowacphomap.h
create mode 100644 isis/src/lro/apps/lrowacphomap/lrowacphomap.xml
create mode 100644 isis/src/lro/apps/lrowacphomap/main.cpp
create mode 100644 isis/tests/FunctionalTestsLrowacphomap.cpp
create mode 100644 isis/tests/data/lrowacphomap/1x1_70NS_7b_wbhs_albflt_grid_geirist_tcorrect.reduced.cub
create mode 100644 isis/tests/data/lrowacphomap/M1181493219CE.vis.odd.reduced.cub
create mode 100644 isis/tests/data/lrowacphomap/WAC_GLD100_V1.0_GLOBAL_with_LOLA_30M_POLE.16bit.reduced.cub
create mode 100644 isis/tests/data/lrowacphomap/back.reduced.cub
create mode 100644 isis/tests/data/lrowacphomap/hapke_full_reformatted.pvl
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5247a65b9a..b0381395f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ release.
### Added
- Improved functionality of msi2isis and MsiCamera model to support new Eros dataset, including support for Gaskell's SUMSPICE files that adjust timing, pointing and spacecraft position ephemeris. [#4886](https://github.com/USGS-Astrogeology/ISIS3/issues/4886)
+- Re-added and refactored the LRO photometry application lrowacphomap to be callable for testing purposes. Issue: [#4960](https://github.com/USGS-Astrogeology/ISIS3/issues/4960), PR: [#4961](https://github.com/USGS-Astrogeology/ISIS3/pull/4961)
### Deprecated
diff --git a/isis/src/lro/apps/lrowacphomap/HapkeLRO.cpp b/isis/src/lro/apps/lrowacphomap/HapkeLRO.cpp
new file mode 100644
index 0000000000..86386233d3
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/HapkeLRO.cpp
@@ -0,0 +1,352 @@
+/**
+ * @file
+ * $Revision$
+ * $Date$
+ *
+ * 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 & 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
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "Projection.h"
+#include "Camera.h"
+#include "HapkeLRO.h"
+#include "DbProfile.h"
+#include "PvlObject.h"
+#include "Brick.h"
+
+using namespace std;
+
+namespace Isis {
+
+ /**
+ * Create an HapkeLRO photometric object
+ *
+ * @author 2021-08-02 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotometricFunction written by Kris Becker
+ *
+ * @param pvl Photometric parameter files
+ * @param cube Input cube file
+ * @param useCamera Enable using camera from input cube
+ * @param paramMap Parameter cube
+ */
+ HapkeLRO::HapkeLRO(PvlObject &pvl, Cube &cube, bool useCamera, Cube *paramMap)
+ : PhotometricFunction(pvl, cube, useCamera) {
+
+ m_paramMap = paramMap;
+ m_paramProj = m_paramMap->projection();
+ m_currentMapSample = 0;
+ m_currentMapLine = 0;
+ m_currentMapIndex = 0;
+
+ PvlObject phoModel = pvl.findObject("PhotometricModel");
+ m_iRef = phoModel.findKeyword("Incref")[0].toDouble(); // Incidence refernce angle
+ m_eRef = phoModel.findKeyword("EmiRef")[0].toDouble(); // Emission reference angle
+ m_gRef = phoModel.findKeyword("Pharef")[0].toDouble(); // Phase reference angle
+ if (QString(phoModel.findKeyword("Units")[0]).toUpper() == "DEGREES") {
+ m_isDegrees = true;
+ }
+ else {
+ m_isDegrees = false;
+ }
+
+ PvlKeyword center = cube.label()->findGroup("BandBin", Pvl::Traverse)["Center"];
+
+ PvlKeyword paramBandNames = m_paramMap->label()->findGroup("BandBin", Pvl::Traverse).findKeyword("Name");
+
+ // Now go through the Groups looking for all the band parameters
+ for (int i = 0; i < phoModel.groups(); i++) {
+ if (phoModel.group(i).isNamed("Parameters")) {
+ PvlGroup paramGroup = phoModel.group(i);
+ HapkeLRO::Parameters parms;
+
+ parms.bandBinCenter = paramGroup.findKeyword("BandBinCenter")[0].toDouble();
+ for (int j = 0; j < center.size(); j++) {
+ if (center[j] == paramGroup.findKeyword("BandBinCenter")[0]) {
+ parms.band = j + 1;
+ }
+ }
+
+ PvlKeyword bands = paramGroup.findKeyword("Bands");
+ for (int j = 0; j < bands.size(); j++) {
+ parms.mapBands.push_back(bands[j].toInt() - 1);
+ parms.names.push_back(QString(paramBandNames[bands[j].toInt() - 1]).toUpper());
+ parms.values.push_back(0.0);
+ }
+
+ m_bandParameters.push_back(parms);
+ }
+ }
+
+ m_paramBrickCount = paramMap->lineCount() * paramMap->sampleCount();
+ m_paramBricks = new Brick*[m_paramBrickCount];
+ memset(m_paramBricks, 0, m_paramBrickCount * sizeof(Brick*));
+
+ m_normalized = true;
+
+ m_hfunc = "HG";
+ if (phoModel.hasKeyword("Hfunc")) {
+ m_hfunc = phoModel.findKeyword("Hfunc")[0].toUpper();
+ }
+
+ if (m_hfunc != "HG") {
+ QString msg = "Invalid HFunction: " + m_hfunc;
+ throw IException(IException::User, msg, _FILEINFO_);
+ }
+ }
+
+ /**
+ * Destructor
+ */
+ HapkeLRO::~HapkeLRO() {
+ for (int i = 0; i < m_paramBrickCount; i++) {
+ delete m_paramBricks[i];
+ }
+
+ delete [] m_paramBricks;
+ }
+
+
+ bool HapkeLRO::normalized() const {
+ return m_normalized;
+ }
+
+
+ /**
+ * @brief Return photometric property given angles
+ *
+ * This method computes the photometric property at the given
+ * cube location after ensuring a proper parameter container is
+ * found for the specified band.
+ *
+ * @author 2021-08-18 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param i Incidence angle at cube location
+ * @param e Emission angle at cube location
+ * @param g Phase angle at cube location
+ * @param band Band number in cube (actually is band index) for lookup purposes
+ *
+ * @return double Photometric correction using parameters
+ */
+ double HapkeLRO::photometry(double i, double e, double g, int band) const {
+ double lat = m_camera->UniversalLatitude();
+ double lon = m_camera->UniversalLongitude();
+
+ return photometry(i, e, g, lat, lon, band);
+ }
+
+
+ /**
+ * @brief Return photometric property given angles
+ *
+ * This method computes the photometric property at the given
+ * cube location after ensuring a proepr parameter container is
+ * found for the specified band.
+ *
+ * @author 2021-08-18 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-08-03 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param i Incidence angle at cube location
+ * @param e Emission angle at cube location
+ * @param g Phase angle at cube location
+ * @param lat Latitude at cube location
+ * @param lon Longitude at cube location
+ * @param band Band number in cube (actually is band index) for lookup purposes
+ *
+ * @return double Photometric correction using parameters
+ */
+ double HapkeLRO::photometry(double i, double e, double g, double lat, double lon, int band) const {
+ m_paramProj->SetUniversalGround(lat, lon);
+ int intSamp = (int) (m_paramProj->WorldX() + 0.5);
+ int intLine = (int) (m_paramProj->WorldY() + 0.5);
+
+ if (intSamp <= 0 || intSamp > m_paramMap->sampleCount()
+ || intLine <= 0 || intLine > m_paramMap->lineCount()) {
+
+ return 0.0;
+ }
+
+ // If the line and sample of the parameter map are different
+ // we need to update the parameters
+ if (m_currentMapSample != intSamp || m_currentMapLine != intLine) {
+ m_currentMapSample = intSamp;
+ m_currentMapLine = intLine;
+ m_currentMapIndex = 0;
+ // read the data for that line/sample into a brick buffer
+ int paramBrickIndex = (intLine - 1) * m_paramMap->sampleCount() + intSamp;
+
+ // if this brick doesn't exist, create it
+ if (m_paramBricks[paramBrickIndex] == NULL) {
+ Brick *newBrick = new Brick(1, 1, m_paramMap->bandCount(), m_paramMap->pixelType());
+ newBrick->SetBasePosition(intSamp, intLine, 1);
+ m_paramMap->read(*newBrick);
+
+ // store the Brick, so we don't have to read it from the file again
+ m_paramBricks[paramBrickIndex] = newBrick;
+ }
+ Brick &b = *m_paramBricks[paramBrickIndex];
+
+ // And for each parameter, copy the appropriate value
+ for (unsigned int p = 0; p < m_bandParameters.size(); p++) {
+ HapkeLRO::Parameters &parms = m_bandParameters[p];
+
+ if (parms.band == band) {
+ m_currentMapIndex = p;
+ for (unsigned int v = 0; v < parms.values.size(); v++) {
+ parms.values[v] = b[parms.mapBands[v]];
+ if (IsSpecial(parms.values[v])) {
+ return Isis::Null;
+ }
+ }
+ parms.phoStd = photometry(parms, m_iRef, m_eRef, m_gRef);
+ }
+ }
+ }
+
+ double ph = photometry(m_bandParameters[m_currentMapIndex], i, e, g);
+ if (!normalized()) {
+ return ph;
+ }
+
+ return m_bandParameters[m_currentMapIndex].phoStd / ph;
+ }
+
+
+ /**
+ * @brief Performs actual photometric correction calculations
+ *
+ * This routine computes photometric correction using parameters
+ * for the Exponential-Buratti-Hill equation.
+ *
+ * @author 2021-07-29 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from HapkeLRO written by Kris Becker
+ *
+ * @param parms Container of band-specific HapkeLRO parameters
+ * @param i Incidence angle in degrees
+ * @param e Emission angle in degrees
+ * @param g Phase angle in degrees
+ *
+ * @return double Photometric correction parameter
+ */
+ double HapkeLRO::photometry(HapkeLRO::Parameters &parms, double i, double e, double g) const {
+ double w = parms["W"];
+ double bc0 = parms["BCO"];
+ double hc = parms["HC"];
+ double xi = parms["XI"];
+
+ // Ensure problematic values are adjusted
+ if (i == 0.0) {
+ i = 10.E-12;
+ }
+ if (e == 0.0) {
+ e = 10.E-12;
+ }
+
+ // Convert to radians
+ i *= rpd_c();
+ e *= rpd_c();
+
+ double cosg = 0.0;
+ double tang2hc = 0.0;
+ if (!m_isDegrees) {
+ g *= rpd_c();
+ cosg = cos(g);
+ tang2hc = tan(g / 2) / hc;
+ }
+ else {
+ cosg = cos(rpd_c() * g);
+ tang2hc = tan(rpd_c() * g / 2) / hc;
+ }
+
+ // Compute Lommel-Seeliger components
+ double mu = cos(e);
+ double mu0 = cos(i);
+
+ double p = (1 - pow(xi, 2)) / pow(1 - 2 * xi * cosg + pow(xi, 2), 1.5);
+ double Hmu = (1 + 2 * mu) / (1 + 2 * mu * pow(1 - w, 0.5));
+ double Hmu0 = (1 + 2 * mu0) / (1 + 2 * mu0 * pow(1 - w, 0.5));
+ double Bc = (1 + (1 - exp(-1 * tang2hc)) / (tang2hc)) / (2 * pow(1 + tang2hc, 2));
+
+ double rcal = w / 4 * (p + Hmu0 * Hmu - 1) * (1 + bc0 * Bc);
+
+ return rcal * mu0 / (mu0 + mu);
+ }
+
+
+
+ /**
+ * @brief Return parameters used for all bands
+ *
+ * This method creates keyword vectors of band-specific parameters used
+ * in the photometric correction.
+ *
+ * @author 2021-08-03 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Adapted code from HapkeLRO written by Kris Becker
+ * @history 2021-07-19 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param pvl Output PVL container write keywords
+ */
+ void HapkeLRO::report(PvlContainer &pvl) {
+ pvl.addComment(QString("IoF/LS = w/4 * (p(g) + H(mu0,w)*H(mu,w)-1) * (1+Bc0*Bc(g,h))"));
+ pvl.addComment(QString(" where:"));
+ pvl.addComment(QString(" p(g) = (1-xi^2)/(1-2*xi*cos(g) + xi^2)^(3/2)"));
+ pvl.addComment(QString(" H(x,w) = (1+2*x)/(1+2*x*sqrt(1-w))"));
+ pvl.addComment(QString(" Bc(g,h) = (1 + (1-exp(-tan(g/2)/h))/(tan(g/2)/h))/(2*(1+tan(g/2)/h)^2)"));
+
+ pvl += PvlKeyword("Algorithm", "HapkeLRO");
+ pvl += PvlKeyword("ParameterMapCube", m_paramMap->fileName());
+ pvl += PvlKeyword("IncRef", toString(m_iRef), "degrees");
+ pvl += PvlKeyword("EmiRef", toString(m_eRef), "degrees");
+ pvl += PvlKeyword("PhaRef", toString(m_gRef), "degrees");
+ PvlKeyword units("Units");
+
+ if (m_isDegrees) {
+ units += "Degrees";
+ }
+ else {
+ units += "Radians";
+ }
+
+ return;
+ }
+
+
+ void HapkeLRO::setNormalized(bool normalized) {
+ m_normalized = normalized;
+ }
+
+} // namespace Isis
diff --git a/isis/src/lro/apps/lrowacphomap/HapkeLRO.h b/isis/src/lro/apps/lrowacphomap/HapkeLRO.h
new file mode 100644
index 0000000000..544ace4e57
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/HapkeLRO.h
@@ -0,0 +1,123 @@
+#ifndef hapkelro_h
+#define hapkelro_h
+/**
+ * @file
+ * $Revision$
+ * $Date$
+ *
+ * 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 & 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
+#include
+#include
+#include
+
+#include "PhotometricFunction.h"
+#include "Camera.h"
+#include "SpecialPixel.h"
+
+namespace Isis {
+
+ class PvlObject;
+ class Camera;
+ class Brick;
+ class Projection;
+
+
+ /**
+ * @brief An implementation of the HapkeLRO photometric function
+ *
+ * This class implements the Exponential-Buratti-Hill photometric
+ * equation as outline in thier paper "Multispectral Photometry
+ * of the Moon and Absolute Calibration of the Clementine UV/VIS
+ * Camera", published in Icaris v141, pg. 205-255 (1999).
+ *
+ * @author 2021-08-18 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotometricFunction and HapkeLRO written by Kris Becker
+ *
+ */
+ class HapkeLRO : public PhotometricFunction {
+ public:
+ HapkeLRO(PvlObject &pvl, Cube &cube, bool useCamera, Cube *paramMap);
+ ~HapkeLRO();
+ bool normalized() const;
+ double photometry(double i, double e, double g, int band = 1) const override;
+ double photometry(double i, double e, double g, double lat, double lon, int band = 1) const;
+ void report(PvlContainer &pvl);
+ void setNormalized(bool normalized);
+
+ private:
+ QString m_hfunc;
+
+ bool m_isDegrees;
+
+ bool m_normalized;
+
+ Cube *m_paramMap;
+ mutable Projection *m_paramProj;
+ mutable int m_currentMapSample, m_currentMapLine, m_currentMapIndex;
+
+ mutable Brick **m_paramBricks;
+ int m_paramBrickCount;
+
+
+ /**
+ * Container for band photometric correction parameters
+ *
+ * @author 2021-07-30 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotoemtricFunction written by Kris Becker
+ */
+ class Parameters
+ {
+ public:
+ Parameters()
+ : band(1), bandBinCenter(0.0), mapBands(),
+ names(), phoStd(0.0), values() {}
+
+ ~Parameters() {}
+
+ double operator[] (const QString &name) const {
+ for (unsigned int i = 0; i < names.size(); i++) {
+ if (names[i] == name) {
+ return values[i];
+ }
+ }
+
+ QString msg = "No parameter band named '" + name + "'.";
+ throw IException(Isis::IException::User, msg, _FILEINFO_);
+ }
+
+ int band; // band number
+ double bandBinCenter; // center wavelength
+ std::vector mapBands; // parameter bands
+ std::vector names; // parameter names
+ double phoStd; // computed photometric std
+ std::vector values; // parameter values
+ };
+
+ mutable std::vector m_bandParameters; // Parameters for each band
+ double photometry(Parameters &parms, double i, double e, double g) const;
+ };
+};
+
+#endif
\ No newline at end of file
diff --git a/isis/src/lro/apps/lrowacphomap/HapkeLROC.cpp b/isis/src/lro/apps/lrowacphomap/HapkeLROC.cpp
new file mode 100644
index 0000000000..2cb022a056
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/HapkeLROC.cpp
@@ -0,0 +1,638 @@
+/**
+ * @file
+ * $Revision$
+ * $Date$
+ *
+ * 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 & 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 "Projection.h"
+#include "Camera.h"
+#include "HapkeLROC.h"
+#include "PhotoModelFactory.h"
+#include "PhotoModel.h"
+#include "PvlObject.h"
+#include "Brick.h"
+
+using namespace std;
+
+namespace Isis {
+
+ /**
+ * Create an HapkeLROC photometric object
+ *
+ * @author 2021-08-09 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from HapkeLROC written by Dan Clarke
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotometricFunction written by Kris Becker
+ *
+ * @param pvl Photometric parameter files
+ * @param cube Input cube file
+ * @param useCamera Enable using camera from input cube
+ * @param paramMap Parameter cube
+ */
+ HapkeLROC::HapkeLROC(PvlObject &pvl, Cube &cube, bool useCamera, Cube *paramMap)
+ : PhotometricFunction(pvl, cube, useCamera) {
+
+ m_paramMap = paramMap;
+ m_paramProj = m_paramMap->projection();
+ m_currentMapSample = 0;
+ m_currentMapLine = 0;
+ m_currentMapIndex = 0;
+
+ PvlObject phoModel = pvl.findObject("PhotometricModel");
+ m_iRef = phoModel.findKeyword("Incref")[0].toDouble(); // Incidence refernce angle
+ m_eRef = phoModel.findKeyword("EmiRef")[0].toDouble(); // Emission reference angle
+ m_gRef = phoModel.findKeyword("Pharef")[0].toDouble(); // Phase reference angle
+
+ if (QString(phoModel.findKeyword("Units")[0]).toUpper() == "DEGREES") {
+ m_isDegrees = true;
+ }
+ else {
+ m_isDegrees = false;
+ }
+
+ PvlKeyword center = cube.label()->findGroup("BandBin", Pvl::Traverse)["Center"];
+
+ PvlKeyword paramBandNames = m_paramMap->label()->findGroup("BandBin", Pvl::Traverse).findKeyword("Name");
+
+ // Now go through the Groups looking for all the band parameters
+ for (int i = 0; i < phoModel.groups(); i++) {
+ if (phoModel.group(i).isNamed("Parameters")) {
+ PvlGroup paramGroup = phoModel.group(i);
+ HapkeLROC::Parameters parms;
+
+ parms.bandBinCenter = paramGroup.findKeyword("BandBinCenter")[0].toDouble();
+ for (int j = 0; j < center.size(); j++) {
+ if (center[j] == paramGroup.findKeyword("BandBinCenter")[0]) {
+ parms.band = j + 1;
+ }
+ }
+
+ PvlKeyword bands = paramGroup.findKeyword("Bands");
+ for (int j = 0; j < bands.size(); j++) {
+ parms.mapBands.push_back(bands[j].toInt() - 1);
+ parms.names.push_back(QString(paramBandNames[bands[j].toInt() - 1]).toUpper());
+ parms.values.push_back(0.0);
+ }
+
+ m_bandParameters.push_back(parms);
+ }
+ }
+
+ m_paramBrickCount = paramMap->lineCount() * paramMap->sampleCount();
+ m_paramBricks = new Brick*[m_paramBrickCount];
+ memset(m_paramBricks, 0, m_paramBrickCount * sizeof(Brick*));
+
+ m_normalized = true;
+
+ m_photoThetaold = -999.0;
+ m_oldPhase = -9999;
+ m_oldIncidence = -9999;
+ m_oldEmission = -9999;
+
+ m_hfunc = "HG";
+ if (phoModel.hasKeyword("Hfunc")) {
+ m_hfunc = phoModel.findKeyword("Hfunc")[0].toUpper();
+ }
+
+ if (m_hfunc != "HG") {
+ QString msg = "Invalid HFunction: " + m_hfunc;
+ throw IException(IException::User, msg, _FILEINFO_);
+ }
+ }
+
+ /**
+ * Destructor
+ */
+ HapkeLROC::~HapkeLROC() {
+ for (int i = 0; i < m_paramBrickCount; i++) {
+ delete m_paramBricks[i];
+ }
+
+ delete [] m_paramBricks;
+ };
+
+ // { [ 1-2 r x ] } -1
+ // { | 0 / 1+x \ | }
+ // H(x) ~= { - w x | r + -------- ln | --- | | }
+ // { [ 0 2 \ x / ] }
+ // where r is,
+ // 0
+ // 1 - sqrt(1-w)
+ // r = ------------- (precalculated and passed in for speed)
+ // 0 1 + sqrt(1-w)
+ //
+ inline double HGfunc(double x, double w, double r0) {
+ if (x == 0.0) return 1.0;
+ x = abs(x);
+ double hxw = 1.0 - w * x * (r0 + ((1.0 - 2.0 * r0 * x) * 0.5 * log((1.0 + x) / x)));
+
+ return 1.0 / hxw;
+ }
+
+
+ /**
+ * @brief Return photometric property given angles
+ *
+ * This method computes the photometric property at the given
+ * cube location after ensuring a proper parameter container is
+ * found for the specified band.
+ *
+ * @author 2021-08-18 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Adapted code from HapkeLROC written by Dan Clarke
+ * @history 2021-07-19 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param i Incidence angle at cube location
+ * @param e Emission angle at cube location
+ * @param g Phase angle at cube location
+ * @param band Band number in cube (actually is band index) for lookup purposes
+ *
+ * @return double Photometric correction using parameters
+ */
+ double HapkeLROC::photometry(double i, double e, double g, int band) const {
+ double lat = m_camera->UniversalLatitude();
+ double lon = m_camera->UniversalLongitude();
+
+ return photometry(i, e, g, lat, lon, band);
+ }
+
+
+ /**
+ * @brief Return photometric property given angles
+ *
+ * This method computes the photometric property at the given
+ * cube location after ensuring a proepr parameter container is
+ * found for the specified band.
+ *
+ * @author 2021-08-18 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-08-03 Cordell Michaud - Adapted code from HapkeLROC written by Dan Clarke
+ * @history 2021-08-03 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param i Incidence angle at cube location
+ * @param e Emission angle at cube location
+ * @param g Phase angle at cube location
+ * @param lat Latitude at cube location
+ * @param lon Longitude at cube location
+ * @param band Band number in cube (actually is band index) for lookup purposes
+ *
+ * @return double Photometric correction using parameters
+ */
+ double HapkeLROC::photometry(double i, double e, double g, double lat, double lon, int band) const {
+ m_paramProj->SetUniversalGround(lat, lon);
+ int intSamp = (int) (m_paramProj->WorldX() + 0.5);
+ int intLine = (int) (m_paramProj->WorldY() + 0.5);
+
+ if (intSamp <= 0 || intSamp > m_paramMap->sampleCount()
+ || intLine <= 0 || intLine > m_paramMap->lineCount()) {
+ return 0.0;
+ }
+
+ // If the line and sample of the parameter map are different
+ // we need to update the parameters
+ if (m_currentMapSample != intSamp || m_currentMapLine != intLine) {
+ m_currentMapSample = intSamp;
+ m_currentMapLine = intLine;
+ m_currentMapIndex = 0;
+ // read the data for that line/sample into a brick buffer
+ int paramBrickIndex = (intLine - 1) * m_paramMap->sampleCount() + intSamp;
+
+ // if this brick doesn't exist, create it
+ if (m_paramBricks[paramBrickIndex] == NULL) {
+ Brick *newBrick = new Brick(1, 1, m_paramMap->bandCount(), m_paramMap->pixelType());
+ newBrick->SetBasePosition(intSamp, intLine, 1);
+ m_paramMap->read(*newBrick);
+
+ // store the Brick, so we don't have to read it from the file again
+ m_paramBricks[paramBrickIndex] = newBrick;
+ }
+ Brick &b = *m_paramBricks[paramBrickIndex];
+
+ // And for each parameter, copy the appropriate value
+ for (unsigned int p = 0; p < m_bandParameters.size(); p++) {
+ HapkeLROC::Parameters &parms = m_bandParameters[p];
+
+ if (parms.band == band) {
+ m_currentMapIndex = p;
+ for (unsigned int v = 0; v < parms.values.size(); v++) {
+ parms.values[v] = b[parms.mapBands[v]];
+ if (IsSpecial(parms.values[v])) {
+ return Isis::Null;
+ }
+ }
+
+ parms.phoStd = photometry(parms, m_iRef, m_eRef, m_gRef);
+ }
+ }
+ }
+
+
+ double ph = photometry(m_bandParameters[m_currentMapIndex], i, e, g);
+ if (!normalized()) {
+ return ph;
+ }
+
+ return m_bandParameters[m_currentMapIndex].phoStd / ph;
+ }
+
+
+ /**
+ * @brief Performs actual photometric correction calculations
+ *
+ * This routine computes photometric correction using a modified
+ * version of the HapkeHen model in Isis.
+ *
+ * @author 2021-08-19 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Adapted code from HapkeLROC written by Dan Clarke
+ *
+ * @param parms Container of band-specific HapkeLROC parameters
+ * @param i Incidence angle (in degrees)
+ * @param e Emission angle (in degrees)
+ * @param g Phase angle (in degrees)
+ *
+ * @return double Photometric correction parameter
+ */
+ double HapkeLROC::photometry (const HapkeLROC::Parameters &parms, double i, double e, double g) const {
+ // cos(i )
+ // w e
+ // r(i,e,g) = K ---- --------------- [p(g) (1 + B B (g)) + M(i ,e )] [1 + B B (g)] S(i,e,g)
+ // 4 cos(i ) + cos(e ) S0 S e e C0 C
+ // e e
+
+ double w = parms["W"]; // Wh
+ double b = parms["B"]; // Hg1
+ double c = parms["C"]; // Hg2
+ double bc0 = parms["BCO"]; // Bc0
+ double hc = parms["HC"]; // hc
+ double bs0 = parms["BSO"]; // B0
+ double hs = parms["HS"]; // Hh
+ double theta = parms["THETA"]; // Theta
+ double phi = parms["PHI"]; // phi
+
+ if (m_oldPhase == g && m_oldIncidence == i && m_oldEmission == e) {
+ return m_result;
+ }
+
+ m_oldPhase = g;
+ m_oldIncidence = i;
+ m_oldEmission = e;
+
+ double invPI = 1.0 / PI;
+
+ // 2/3
+ // -ln(1 - 1.209 / phi )
+ // K = -------------------------
+ // 2/3
+ // 1.209 / phi
+ double k = 1.0;
+ if (phi != 0.0) {
+ double ck = 1.209 * pow(phi, 2.0/3.0);
+ k = -log(1 - ck) / ck;
+ }
+
+ double incrad = i; // incidence angle in radians
+ double emarad = e; // emission angle in radians
+
+ if (m_isDegrees) {
+ g *= DEG2RAD;
+ incrad *= DEG2RAD;
+ emarad *= DEG2RAD;
+
+ // convert theta to radians
+ theta *= DEG2RAD;
+ }
+
+ double cosg = cos(g);
+ double cosi = cos(incrad);
+ double cose = cos(emarad);
+
+ double cost;
+ double sint;
+ double tan2t;
+
+ if (theta != m_photoThetaold) {
+ cost = cos(theta);
+ sint = sin(theta);
+ m_photoCott = cost / max(1.0e-10, sint);
+ m_photoCot2t = m_photoCott * m_photoCott;
+ m_photoTant = sint / cost;
+ tan2t = m_photoTant * m_photoTant;
+ m_invChiThetaP = sqrt(1.0 + PI * tan2t);
+ m_chiThetaP = 1.0 / m_invChiThetaP;
+ m_photoThetaold = theta;
+ }
+
+ if (i >= 90.0) {
+ m_result = 0.0;
+ return m_result;
+ }
+
+ // p(g) is, (H-G phase function)
+ // 2 2
+ // 1 + c 1 - b 1 - c 1 - b
+ // p(g) = ----- ------------------------- + ----- -------------------------
+ // 2 2 3/2 2 2 3/2
+ // (1 - 2b cos(g) + b ) (1 + 2b cos(g) + b )
+ double b2 = b * b;
+ double pg1 = (1.0 + c) * 0.5 * (1.0 - b2) / pow((1.0 - 2.0 * b * cosg + b2), 1.5);
+ double pg2 = (1.0 - c) * 0.5 * (1.0 - b2) / pow((1.0 + 2.0 * b * cosg + b2), 1.5);
+ double pg = pg1 + pg2;
+
+ // B (g) is, (Shadow Hiding Opposition surge Effect: SHOE )
+ // S
+ // B (g) = 1 / [1 + tan(g/2) / h ]
+ // S s
+ double tang2 = tan(g * 0.5);
+ double bsg;
+ if (hs == 0.0 || g == PI) {
+ bsg = 0.0;
+ }
+ else {
+ bsg = 1.0 / (1.0 + tang2 / hs);
+ }
+
+ // B (g) is, (Coherent Backscatter Opposition surge Effect: CBOE)
+ // C
+ // 1 - exp[-tan(g/2) / h ]
+ // c
+ // 1 + -------------------------
+ // tan(g/2) / h
+ // c
+ // B (g) = -------------------------------
+ // C 2
+ // 2[1 + tan(g/2) / h ]
+ // c
+ double bcg = 0.0;
+ if (g == 0.0) { // if g is 0, bcg is undefined, so set it to 1
+ bcg = 1.0;
+ }
+ else if (g != PI && hc != 0.0) {
+ double tang2hc = tang2 / hc;
+ bcg = (1 + ((1 - exp(-tang2hc)) / tang2hc)) / (2 * (1 + tang2hc) * (1 + tang2hc));
+ }
+
+ // If smooth Hapke is wanted then set Theta<=0.0
+ if (theta <= 0.0) {
+ double mie = 0.0;
+ if (m_hfunc == "HG") {
+ // 1 - sqrt(1 - w)
+ // r = --------------- (precalculated and passed in to HGfunc for speed, see comments on HGfunc)
+ // 0 1 + sqrt(1 - w)
+ double r0sqrt = sqrt(1.0 - w);
+ double r0 = (1.0 - r0sqrt) / (1.0 + r0sqrt);
+
+ // ( cos(i ) ) ( cos(e ) )
+ // ( e ) ( e )
+ // M(i ,e ) = H( ---------, w ) H( ---------, w ) - 1
+ // e e ( K ) ( K )
+ double invK = 1.0 / k;
+ mie = HGfunc(cosi*invK, w, r0) * HGfunc(cose*invK, w, r0) - 1.0;
+ }
+
+ // w cos(i)
+ // r(i,e,g) = K ---- --------------- [p(g) (1 + B B (g)) + M(i ,e )] [1 + B B (g)]
+ // 4 cos(i) + cos(e) S0 S e e C0 C
+ m_result = k * (w / 4.0) * (cosi / (cosi + cose)) * (pg * (1.0 + bs0*bsg) + mie) * (1.0 + bc0*bcg);
+
+ return m_result;
+ }
+
+ // [ 2 _____ ]
+ // E (i) = exp| - ---- cot(theta ) cot(i) | = e1i
+ // 1 [ pi p ]
+ //
+ // [ 1 2 _____ 2 ]
+ // E (i) = exp| - ---- cot (theta ) cot (i) | = e2i
+ // 2 [ pi p ]
+ double sini = sin(incrad);
+ double coti = cosi / max(1.0e-10, sini);
+ double e1i = exp(min(-2.0 * m_photoCott * coti * invPI, 23.0));
+ double cot2i = coti * coti;
+ double e2i = exp(min(-m_photoCot2t * cot2i * invPI, 23.0));
+
+
+ // [ E (i) ]
+ // _____ | _____ 2 |
+ // eta(i) = chi(theta )| cos(i) + sin(i) tan(theta ) ----------- |
+ // p | p 2 - E (i) |
+ // [ 1 ]
+ double etai = m_chiThetaP * (cosi + sini * m_photoTant * e2i / (2.0 - e1i));
+
+ double sine = sin(emarad);
+ double sinei = sine * sini;
+
+ // [ / psi \ ]
+ // f(psi) = exp| -2 tan| ---- | |
+ // [ \ 2 / ]
+ //
+ // [ cos(g) - cos(e) cos(i) ]
+ // psi = arccos| ------------------------ |
+ // [ sin(e) sin(i) ]
+ double cospsi;
+ double psi;
+
+ if (sinei == 0.0) {
+ cospsi = 1.0;
+ psi = 0.0;
+ }
+ else {
+ cospsi = (cosg - cose * cosi) / sinei;
+ if (cospsi <= -1.0) {
+ psi = PI;
+ }
+ else if (cospsi > 1.0) {
+ psi = 0.0;
+ }
+ else {
+ psi = acos(cospsi);
+ }
+ }
+
+ double halfpsi = psi * 0.5;
+ double fpsi;
+ if (halfpsi >= HALFPI) {
+ fpsi = 0.0;
+ }
+ else {
+ double tanhalfpsi = tan(halfpsi);
+ fpsi = exp(min(-2.0 * tanhalfpsi, 23.0));
+ }
+
+ // [ 2 _____ ]
+ // E (e) = exp| - ---- cot(theta ) cot(e) | = e1e
+ // 1 [ pi p ]
+ //
+ // [ 1 2 _____ 2 ]
+ // E (e) = exp| - ---- cot (theta ) cot (e) | = e2e
+ // 2 [ pi p ]
+ double cote = cose / max(1.0e-10, sine);
+ double e1e = exp(min(-2.0 * m_photoCott * cote * invPI, 23.0));
+ double cot2e = cote * cote;
+ double e2e = exp(min(-m_photoCot2t * cot2e * invPI, 23.0));
+
+
+ // [ E (e) ]
+ // _____ | _____ 2 |
+ // eta(e) = chi(theta )| cos(e) + sin(e) tan(theta ) ----------- |
+ // p | p 2 - E (e) |
+ // [ 1 ]
+ double etae = m_chiThetaP * (cose + sine * m_photoTant * e2e / (2.0 - e1e));
+
+ double sin2psi2 = sin(halfpsi);
+ sin2psi2 *= sin2psi2;
+ double psiOverPI = psi * invPI;
+
+ double mu0e;
+ double mue;
+ double ecei;
+ double s2ei;
+ double ecee;
+ double s2ee;
+ if (i <= e) {
+ // [ 2 ]
+ // | cos(psi) E (e) + sin (psi/2) E (i) |
+ // _____ | _____ 2 2 |
+ // mu = cos(i ) = chi(theta )| cos(i) + sin(i) tan(theta ) ------------------------------------ |
+ // 0e e p | p 2 - E (e) - (psi/pi) E (i) |
+ // [ 1 1 ]
+ //
+ // [ 2 ]
+ // | E (e) - sin (psi/2) E (i) |
+ // _____ | _____ 2 2 |
+ // mu = cos(e ) = chi(theta )| cos(e) + sin(e) tan(theta ) ---------------------------- |
+ // e e p | p 2 - E (e) - (psi/pi) E (i) |
+ // [ 1 1 ]
+ ecei = (2.0 - e1e - psiOverPI * e1i);
+ s2ei = sin2psi2 * e2i;
+ mu0e = m_chiThetaP * (cosi + sini * m_photoTant * (cospsi * e2e + s2ei) / ecei);
+ mue = m_chiThetaP * (cose + sine * m_photoTant * (e2e - s2ei) / ecei);
+ }
+ else {
+ // [ 2 ]
+ // | E (i) - sin (psi/2) E (e) |
+ // _____ | _____ 2 2 |
+ // mu = cos(i ) = chi(theta )| cos(i) + sin(i) tan(theta ) ---------------------------- |
+ // 0e e p | p 2 - E (i) - (psi/pi) E (e) |
+ // [ 1 1 ]
+ //
+ // [ 2 ]
+ // | cos(psi) E (i) + sin (psi/2) E (e) |
+ // _____ | _____ 2 2 |
+ // mu = cos(e ) = chi(theta )| cos(e) + sin(e) tan(theta ) ------------------------------------ |
+ // e e p | p 2 - E (i) - (psi/pi) E (e) |
+ // [ 1 1 ]
+ ecee = (2.0 - e1i - psiOverPI * e1e);
+ s2ee = sin2psi2 * e2e;
+ mu0e = m_chiThetaP * (cosi + sini * m_photoTant * (e2i - s2ee) / ecee);
+ mue = m_chiThetaP * (cose + sine * m_photoTant * (cospsi * e2i + s2ee) / ecee);
+ }
+
+ double mie = 0.0;
+ if (m_hfunc == "HG") {
+ // 1 - sqrt(1 - w)
+ // r = --------------- (precalculated and passed in to HGfunc for speed, see comments on HGfunc)
+ // 0 1 + sqrt(1 - w)
+ double r0sqrt = sqrt(1.0 - w);
+ double r0 = (1.0 - r0sqrt) / (1.0 + r0sqrt);
+
+ // ( cos(i ) ) ( cos(e ) )
+ // ( e ) ( e )
+ // M(i ,e ) = H( ---------, w ) H( ---------, w ) - 1
+ // e e ( K ) ( K )
+ double invK = 1.0 / k;
+ mie = HGfunc(mu0e*invK, w, r0) * HGfunc(mue*invK, w, r0) - 1.0;
+ }
+
+ // _____
+ // mu mu chi(theta )
+ // e 0 p
+ // S(i,e,psi) = -------- -------- -----------------------------------
+ // eta(e) eta(i) _____
+ // 1 - f(psi) + f(psi) chi(theta ) q
+ // p
+ double q;
+ if (i <= e) {
+ // q = [mu /eta(i)]
+ // 0
+ q = cosi / etai;
+ }
+ else {
+ // q = [mu/eta(e)]
+ q = cose / etae;
+ }
+ double s = (mue * cosi * m_chiThetaP) / (etae * etai * (1.0 - fpsi + (fpsi * m_chiThetaP * q)));
+
+ // cos(i )
+ // w e
+ // r(i,e,g) = K ---- --------------- [p(g) (1 + B B (g)) + M(i ,e )] [1 + B B (g)] S(i,e,g)
+ // 4 cos(i ) + cos(e ) S0 S e e C0 C
+ // e e
+ m_result = k * (w / 4.0) * (mu0e / (mu0e + mue)) * (pg * (1.0 + bs0*bsg) + mie) * (1.0 + bc0*bcg) * s;
+
+ return m_result;
+ }
+
+
+ bool HapkeLROC::normalized() const {
+ return m_normalized;
+ }
+
+
+ /**
+ * @brief Return parameters used for all bands
+ *
+ * This method creates keyword vectors of band-specific parameters used
+ * in the photometric correction.
+ *
+ * @author 2021-08-03 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-08-02 Cordell Michaud - Adapted code from PhotometricFunction written by Kris Becker
+ *
+ * @param pvl Output PVL container in which to write keywords
+ */
+ void HapkeLROC::report(PvlContainer &pvl) {
+ pvl += PvlKeyword("Algorithm", "HapkeLROC");
+ pvl += PvlKeyword("ParameterMapCube", m_paramMap->fileName());
+ pvl += PvlKeyword("IncRef", toString(m_iRef), "degrees");
+ pvl += PvlKeyword("EmiRef", toString(m_eRef), "degrees");
+ pvl += PvlKeyword("PhaRef", toString(m_gRef), "degrees");
+
+ PvlKeyword units("Units");
+ if (m_isDegrees) {
+ units += "Degrees";
+ }
+ else {
+ units += "Radians";
+ }
+
+ return;
+ }
+
+
+ void HapkeLROC::setNormalized(bool normalized) {
+ m_normalized = normalized;
+ }
+} // namespace Isis
\ No newline at end of file
diff --git a/isis/src/lro/apps/lrowacphomap/HapkeLROC.h b/isis/src/lro/apps/lrowacphomap/HapkeLROC.h
new file mode 100644
index 0000000000..420e8b0b56
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/HapkeLROC.h
@@ -0,0 +1,131 @@
+#ifndef hapkelroc_h
+#define hapkelroc_h
+/**
+ * @file
+ * $Revision$
+ * $Date$
+ *
+ * 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 & 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 "PhotometricFunction.h"
+
+namespace Isis {
+ class PvlObject;
+ class Camera;
+ class Brick;
+ class Projection;
+
+
+ /**
+ * @brief An implementation of the bidirectional reflectance model by Hapke (2012).
+ *
+ * This class is based on the Hapke PhotoModel from Isis base code. Photometry method
+ * was updated to match full Hapke (2012) model and some variable names were changed
+ * to match reference formula.
+ *
+ * Reference:
+ * Hapke, B. (2012) Theory of Reflectance and Emittance Spectroscopy, Cambridge Univ. Press.
+ *
+ * @author 2021-08-18 Cordell Michaud
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from HapkeLROC written by Dan Clarke
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotometricFunction written by Kris Becker
+ *
+ */
+ class HapkeLROC : public PhotometricFunction {
+ public:
+ HapkeLROC(PvlObject &pvl, Cube &cube, bool useCamera, Cube *paramMap);
+ ~HapkeLROC();
+
+ bool normalized() const;
+ double photometry(double i, double e, double g, int band = 1) const override;
+ double photometry(double i, double e, double g, double lat, double lon, int band = 1) const;
+ void report(PvlContainer &pvl);
+ void setNormalized(bool normalized);
+
+ private:
+ QString m_hfunc;
+
+ bool m_isDegrees;
+
+ bool m_normalized;
+
+ Cube *m_paramMap;
+ mutable Projection *m_paramProj;
+ mutable int m_currentMapSample, m_currentMapLine, m_currentMapIndex;
+
+ mutable Brick **m_paramBricks;
+ int m_paramBrickCount;
+
+ mutable double m_chiThetaP;
+ mutable double m_invChiThetaP;
+ mutable double m_oldPhase;
+ mutable double m_oldIncidence;
+ mutable double m_oldEmission;
+ mutable double m_photoB0save;
+ mutable double m_photoCot2t;
+ mutable double m_photoCott;
+ mutable double m_photoTant;
+ mutable double m_photoThetaold;
+
+ mutable double m_result;
+
+
+ /**
+ * Container for band photometric correction parameters
+ *
+ * @author 2021-08-02 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from PhotometricFunction written by Kris Becker
+ */
+ class Parameters {
+ public:
+ Parameters()
+ : band(1), bandBinCenter(0.0), mapBands(),
+ names(), phoStd(0.0), values() {}
+
+ ~Parameters() {}
+
+ double operator[] (const QString &name) const {
+ for (unsigned int i = 0; i < names.size(); i++) {
+ if (names[i] == name) {
+ return values[i];
+ }
+ }
+
+ QString msg = "No parameter band named '" + name + "'.";
+ throw IException(Isis::IException::User, msg, _FILEINFO_);
+ }
+
+ int band; // band number
+ double bandBinCenter; // center wavelength
+ std::vector mapBands; // parameter bands
+ std::vector names; // parameter names
+ double phoStd; // computed photometric std
+ std::vector values; // parameter values
+ };
+
+ mutable std::vector m_bandParameters; // Parameters for each band
+ double photometry(const Parameters &parms, double i, double e, double g) const;
+ };
+
+};
+
+#endif
\ No newline at end of file
diff --git a/isis/src/lro/apps/lrowacphomap/Makefile b/isis/src/lro/apps/lrowacphomap/Makefile
new file mode 100644
index 0000000000..1ef921dfe2
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/Makefile
@@ -0,0 +1,7 @@
+ifeq ($(ISISROOT), $(BLANK))
+.SILENT:
+error:
+ echo "Please set ISISROOT";
+else
+ include $(ISISROOT)/make/isismake.apps
+endif
diff --git a/isis/src/lro/apps/lrowacphomap/lrowacphomap.cpp b/isis/src/lro/apps/lrowacphomap/lrowacphomap.cpp
new file mode 100644
index 0000000000..f71105f188
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/lrowacphomap.cpp
@@ -0,0 +1,263 @@
+#include
+
+#include "HapkeLRO.h"
+#include "HapkeLROC.h"
+#include "IException.h"
+#include "PhotometricFunction.h"
+#include "ProcessByBrick.h"
+#include "PvlGroup.h"
+#include "SpecialPixel.h"
+
+#include "lrowacphomap.h"
+
+using namespace std;
+using namespace Isis;
+
+
+namespace Isis {
+ Pvl lrowacphomap(UserInterface &ui) {
+ // Set up the input cube
+ Cube *icube = new Cube();
+ CubeAttributeInput inAtt = ui.GetInputAttribute("FROM");
+ if (inAtt.bands().size() != 0) {
+ icube->setVirtualBands(inAtt.bands());
+ }
+ icube->open(ui.GetCubeName("FROM"));
+
+ Pvl log = lrowacphomap(icube, ui);
+
+ delete icube;
+ icube = NULL;
+
+ return log;
+ }
+
+ Pvl lrowacphomap(Cube *icube, UserInterface &ui) {
+ Pvl log;
+
+ HapkeLRO *phoLro = NULL;
+ HapkeLROC *phoLroc = NULL;
+ QString algoName;
+
+ bool normalized;
+ bool useDem;
+ bool photometryOnly;
+
+ ProcessByBrick p;
+
+ p.SetInputCube(icube);
+
+ // Set up the output cube
+ Cube *ocube = p.SetOutputCube(ui.GetCubeName("TO"), ui.GetOutputAttribute("TO"));
+
+ bool useBackplane = false;
+
+ if (ui.WasEntered("BACKPLANE")) {
+ if (icube->bandCount() != 1) {
+ string msg = "Invalid Image: The backplane option can only be used with a single image band at a time.";
+ throw IException(IException::User, msg, _FILEINFO_);
+ }
+
+ CubeAttributeInput backplaneCai = ui.GetInputAttribute("BACKPLANE");
+
+ vector bands = backplaneCai.bands();
+
+ if (bands.size() == 0) {
+ bands.clear();
+ bands.push_back("1");
+ bands.push_back("2");
+ bands.push_back("3");
+ bands.push_back("4");
+ bands.push_back("5");
+ }
+ else if (bands.size() != 5) {
+ string msg = "Invalid Backplane: The backplane must be exactly 5 bands";
+ throw IException(IException::User, msg, _FILEINFO_);
+ }
+
+ CubeAttributeInput cai;
+ cai.setAttributes("+" + bands[0]);
+ p.SetInputCube(ui.GetCubeName("BACKPLANE"), cai);
+ cai.setAttributes("+" + bands[1]);
+ p.SetInputCube(ui.GetCubeName("BACKPLANE"), cai);
+ cai.setAttributes("+" + bands[2]);
+ p.SetInputCube(ui.GetCubeName("BACKPLANE"), cai);
+ cai.setAttributes("+" + bands[3]);
+ p.SetInputCube(ui.GetCubeName("BACKPLANE"), cai);
+ cai.setAttributes("+" + bands[4]);
+ p.SetInputCube(ui.GetCubeName("BACKPLANE"), cai);
+
+ useBackplane = true;
+ }
+
+ // Get the name of the parameter files
+ Pvl par(ui.GetFileName("PHOALGO"));
+ Cube *parCube = new Cube();
+ CubeAttributeInput parCubeAtt = ui.GetInputAttribute("PHOPARCUBE");
+ if (parCubeAtt.bands().size() != 0) {
+ parCube->setVirtualBands(parCubeAtt.bands());
+ }
+ parCube->open(ui.GetCubeName("PHOPARCUBE"));
+
+ p.SetBrickSize(128, 128, icube->bandCount());
+
+ algoName = PhotometricFunction::algorithmName(par).toUpper();
+
+ photometryOnly = ui.GetBoolean("PHOTOMETRYONLY");
+ normalized = ui.GetBoolean("NORMALIZED");
+
+ if (algoName == "HAPKELRO") {
+ phoLro = new HapkeLRO(par, *icube, !useBackplane, parCube);
+ phoLro->setMinimumPhaseAngle(ui.GetDouble("MINPHASE"));
+ phoLro->setMaximumPhaseAngle(ui.GetDouble("MAXPHASE"));
+ phoLro->setMinimumEmissionAngle(ui.GetDouble("MINEMISSION"));
+ phoLro->setMaximumEmissionAngle(ui.GetDouble("MAXEMISSION"));
+ phoLro->setMinimumIncidenceAngle(ui.GetDouble("MININCIDENCE"));
+ phoLro->setMaximumIncidenceAngle(ui.GetDouble("MAXINCIDENCE"));
+
+ phoLro->setNormalized(normalized);
+ }
+ else if (algoName == "HAPKELROC") {
+ phoLroc = new HapkeLROC(par, *icube, !useBackplane, parCube);
+ phoLroc->setMinimumPhaseAngle(ui.GetDouble("MINPHASE"));
+ phoLroc->setMaximumPhaseAngle(ui.GetDouble("MAXPHASE"));
+ phoLroc->setMinimumEmissionAngle(ui.GetDouble("MINEMISSION"));
+ phoLroc->setMaximumEmissionAngle(ui.GetDouble("MAXEMISSION"));
+ phoLroc->setMinimumIncidenceAngle(ui.GetDouble("MININCIDENCE"));
+ phoLroc->setMaximumIncidenceAngle(ui.GetDouble("MAXINCIDENCE"));
+
+ phoLroc->setNormalized(normalized);
+ }
+ else {
+ QString msg = " Algorithm Name [" + algoName + "] not recognized. ";
+ msg += "Compatible Algorithms are:\n HapkeLRO\n HapkeLROC";
+ throw IException(IException::User, msg, _FILEINFO_);
+ }
+
+ // Determine how photometric angles should be calculated
+ useDem = ui.GetBoolean("USEDEM");
+
+ /**
+ * @brief Apply photometric correction
+ *
+ * Short function dispatched for each brick to apply the photometrc
+ * correction function.
+ *
+ * @author 2021-08-02 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from lrowacphomap by Kris Becker
+ *
+ * @param in Buffer containing input data
+ * @param out Buffer of photometrically corrected data
+ */
+ auto phoCal = [&phoLro, &phoLroc, algoName, useDem, photometryOnly](Buffer &in, Buffer &out) -> void {
+ for (int i = 0; i < in.size(); i++) {
+ // Don't correct special pixels
+ if (IsSpecial(in[i])) {
+ out[i] = in[i];
+ }
+ else {
+ // Get correction and test for validity
+ double ph = Null;
+ if (algoName == "HAPKELRO") {
+ ph = phoLro->compute(in.Line(i), in.Sample(i), in.Band(i), useDem);
+ }
+ else if (algoName == "HAPKELROC") {
+ ph = phoLroc->compute(in.Line(i), in.Sample(i), in.Band(i), useDem);
+ }
+
+ if (!photometryOnly) {
+ out[i] = (IsSpecial(ph) ? Null : in[i] * ph);
+ }
+ else {
+ out[i] = (IsSpecial(ph) ? Null : ph);
+ }
+ }
+ }
+
+ return;
+ };
+
+ /**
+ * @brief Apply photometric correction with backplane
+ *
+ * Short function dispatched for each brick to apply the photometrc
+ * correction function with a backplane.
+ *
+ * @author 2021-08-02 Cordell Michaud
+ *
+ * @internal
+ * @history 2021-07-19 Cordell Michaud - Code adapted from lrowacphomap by Kris Becker
+ *
+ * @param in Buffer containing input data
+ * @param out Buffer of photometrically corrected data
+ */
+ auto phoCalWithBackplane = [&phoLro, &phoLroc, algoName, photometryOnly](std::vector &in, std::vector &out) -> void {
+
+ Buffer &image = *in[0];
+ Buffer &phase = *in[1];
+ Buffer &emission = *in[2];
+ Buffer &incidence = *in[3];
+ Buffer &lat = *in[4];
+ Buffer &lon = *in[5];
+ Buffer &calibrated = *out[0];
+
+ for (int i = 0; i < image.size(); i++) {
+ // Don't correct special pixels
+ if (IsSpecial(image[i])) {
+ calibrated[i] = image[i];
+ }
+ else if (IsSpecial(phase[i]) || IsSpecial(emission[i]) || IsSpecial(incidence[i]) || IsSpecial(lat[i]) || IsSpecial(lon[i])) {
+ calibrated[i] = Isis::Null;
+ }
+ else {
+ // Get correction and test for validity
+ double ph = Null;
+ if (algoName == "HAPKELRO") {
+ ph = phoLro->photometry(incidence[i], emission[i], phase[i], lat[i], lon[i], image.Band(i));
+ }
+ else if (algoName == "HAPKELROC") {
+ ph = phoLroc->photometry(incidence[i], emission[i], phase[i], lat[i], lon[i], image.Band(i));
+ }
+
+ if (!photometryOnly) {
+ calibrated[i] = (IsSpecial(ph) ? Null : image[i] * ph);
+ }
+ else {
+ calibrated[i] = (IsSpecial(ph) ? Null : ph);
+ }
+ }
+ }
+
+ return;
+ };
+
+
+ // Start the processing
+ if (useBackplane) {
+ p.ProcessCubes(phoCalWithBackplane, false);
+ }
+ else {
+ p.ProcessCube(phoCal, false);
+ }
+
+ PvlGroup photo("Photometry");
+ if (algoName == "HAPKELRO") {
+ phoLro->report(photo);
+ }
+ else if (algoName == "HAPKELROC") {
+ phoLroc->report(photo);
+ }
+
+ ocube->putGroup(photo);
+ log.addGroup(photo);
+
+ delete phoLro;
+ phoLro = NULL;
+ delete phoLroc;
+ phoLroc = NULL;
+
+ return log;
+ }
+}
\ No newline at end of file
diff --git a/isis/src/lro/apps/lrowacphomap/lrowacphomap.h b/isis/src/lro/apps/lrowacphomap/lrowacphomap.h
new file mode 100644
index 0000000000..8310893bed
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/lrowacphomap.h
@@ -0,0 +1,14 @@
+#ifndef lrowacphomap_h
+#define lrowacphomap_h
+
+#include "Cube.h"
+#include "Pvl.h"
+#include "UserInterface.h"
+
+
+namespace Isis {
+ extern Pvl lrowacphomap(UserInterface &ui);
+ extern Pvl lrowacphomap(Cube *icube, UserInterface &ui);
+}
+
+#endif
\ No newline at end of file
diff --git a/isis/src/lro/apps/lrowacphomap/lrowacphomap.xml b/isis/src/lro/apps/lrowacphomap/lrowacphomap.xml
new file mode 100644
index 0000000000..db5abe6ae6
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/lrowacphomap.xml
@@ -0,0 +1,483 @@
+
+
+
+
+
+ Apply Hapke bidirectional reflectance model photometric correction to multiband cubes
+
+
+
+
+ lrowacphomap implements photometric correction based on the full form of bidirectional reflectance model by Hapke (2012). See below for
+ full description of the model. It takes an input cube to be corrected, an output cube to write the results to, a parameter cube that defines the Hapke parameters
+ to be used for the correction, a PVL input file to define the algorithm and an optional backplane cube. Here's an example command line:
+
+
+ lrowacphomap from=input.cub+1 to=output.cub phoalgo=hapke.pvl phoparcube=params.cub backplane=input.cub+2,3,4,5,6
+
+
+ The input cube (from) must have a BandBin label.
+
+
+ Example (containing image and backplane bands):
+
+ Group = BandBin
+ FilterNumber = (1, 2, 3, 4, 5, 6)
+ Center = (321, 321, 321, 321, 321, 321)
+ Width = (32, 32, 32, 32, 32, 32)
+ Name = (Image, Phase, Emission, Incidence, lat, lon)
+ End_Group
+
+
+ The PVL file (phoalgo) contains the algorithm name, reference values for normalization, and a Parameters group to define the parameter bands from the parameter cube to use.
+
+ Example:
+
+ Object = PhotometricModel
+ Name = HapkeLROC
+ Units = Degrees
+ Incref = 60.0
+ Emiref = 0.0
+ Pharef = 60.0
+
+ Group = Parameters
+ BandBinCenter = 321
+ Bands = (1, 2, 3, 4, 5, 6, 7, 8, 9)
+ EndGroup
+ EndObject
+
+
+ The Incref, Emiref and Pharef are the incidence, emission and phase angles to be used as the common illumination and viewing angles. It will be used to normalize
+ the photometric correction parameter to these angles. The equation used to create the photometrically corrected I/F is:
+
+ n_IOF = IOF / Model(g,e,i) * Model(Pharef,Emiref,Incref)
+
+ where Model(g,e,i) is the model value for phase (g), emission (e), and incidence (i) at each pixel,
+ Model(Pharef,Emiref,Incref) is the model value at the given common geometric angles.
+
+
+
+
+ The parameter cube (phoparcube) should be a 9 band cube with the parameters in the order:
+
+
+
+
+ backplane cube must contain 5 bands (in order): Phase Angle, Emission Angle, Incidence Angle, Latitude, Longitude
+
+
+ Hapke Bidirectional Reflectance Model
+
+
+ In full form of bidirectional reflectance model by Hapke (2012), the radiance factor I/F is
+
+ I is radiance, F is perfectly diffuse surface illuminated at i = e = g = 0 (corresponds to the flat-field).
+
+
+
+ The bidirectional reflectance r(i,e,g) is
+
+ where i is incidence angle, e is emission angle, and g is phase angle, ie and ee are the effective angle of incidence and emission respectively.
+
+
+
+ Then, K is (porosity effect)
+
+
+
+
+
+
+
+ p(g) is, (H-G phase function)
+
+
+
+
+
+
+
+ BS(g) is, (Shadow Hiding Opposition surge Effect: SHOE )
+
+
+
+
+
+
+
+ M(i,e) is, (Isotropic Multiple Scattering Approximation: IMSA)
+
+
+
+
+
+
+
+ where H(x) is,
+
+
+
+
+
+
+
+ where r0 is,
+
+
+
+
+
+
+
+ BC(g) is, (Coherent Backscatter Opposition surge Effect: CBOE)
+
+
+
+
+
+
+
+
+ Then, S(i,e,g) is, (Shadow effect)
+
+
+
+ When i e,
+
+
+
+
+
+
+
+ where and are,
+
+
+
+
+
+
+
+
+
+
+
+ When e i,
+
+
+
+
+
+
+
+ where and are,
+
+
+
+
+
+
+
+
+
+
+
+ where is,
+
+
+
+
+
+
+
+ and is, (y corresponds to i or e)
+
+
+
+
+
+
+
+ where E1 and E2 are, (y corresponds to i or e)
+
+
+
+
+
+
+
+
+
+
+
+ is given by,
+
+
+
+
+
+
+
+ is given by,
+
+
+
+
+
+
+
+ Thus it can be written as,
+
+
+
+
+
+
+
+ Finally, free parameters are,
+
+
+
+ w (Single scattering albedo)
+ b (H-G phase function asymmetry)
+ c (H-G phase function amplitude)
+ BC0 (CBOE amplitude)
+ hC (CBOE angular width)
+ BS0 (SHOE amplitude)
+ hS (SHOE angular width)
+ (Roughness parameter)
+ (Filling factor)
+
+
+
+ Reference:
+
+ Hapke, B. (2012) Theory of Reflectance and Emittance Spectroscopy, Cambridge Univ. Press.
+
+
+
+
+
+
+ Original version.
+
+
+ Added full Hapke model.
+ Optimized processing.
+ Updated documentation.
+
+
+ Fixed an error in how parameters were loaded for use in the refrence model calculation.
+
+
+ Refactored lrowacphomap for testability and added functional tests.
+
+
+
+
+ Lunar Reconnaissance Orbiter
+
+
+
+
+
+ cube
+ input
+
+ Input cube
+
+
+ Use this parameter to select the input filename.
+
+
+ *.cub
+
+
+
+
+ cube
+ real
+ output
+
+ Output cube
+
+
+ This file will contain the photometrically corrected image data after
+ being corrected.
+
+
+
+
+ cube
+ real
+ input
+
+ Backplane Cube
+
+
+ This file will contain the backplane data. It must have "Phase Angle", "Emission Angle",
+ "Incidence Angle", "Latitude" and "Longitude".
+
+
+ Calculate the photometry on the fly
+
+
+
+
+ filename
+ input
+
+ Pvl file
+
+
+ This file will contain the photometric correction algorithm information.
+
+ Here's an example pvl file for Hapke photometric correction:
+
+ Object = PhotometricModel
+ Name = HapkeLROC
+ Units = Degrees
+ Incref = 60.0
+ Emiref = 0.0
+ Pharef = 60.0
+
+ Group = Parameters
+ BandBinCenter = 321
+ Bands = (1, 2, 3, 4, 5, 6, 7, 8, 9)
+ EndGroup
+ EndObject
+
+
+ *.pvl
+
+
+ cube
+ input
+
+ Parameter cube
+
+
+ This file will contain the parameters (in order) Wh, Hg1, Hg2, Bc0, hc, B0, Hh, Theta, phi. See the main program
+ documentation for a full description.
+
+ *.cub
+
+
+
+
+
+
+ double
+ - 0.0
+ Minimum phase angle to trim
+
+ Pixels which have a phase angle less than this value will be
+ trimmed.
+
+ 0.0
+ 180.0
+ - MAXPHASE
+
+
+ double
+ - 180.0
+ Maximum phase angle to trim
+
+ Pixels which have a phase angle greater than this value will be
+ trimmed.
+
+ 0.0
+ 180.0
+ - MINPHASE
+
+
+
+ double
+ - 0.0
+ Minimum emission angle to trim
+
+ Pixels which have an emission angle less than this value will be
+ trimmed.
+
+ 0.0
+ 90.0
+ - MAXEMISSION
+
+
+ - 90.0
+ double
+ Maximum emission angle to trim
+
+ Pixels which have a emission angle greater than this value will be
+ trimmed.
+
+ 0.0
+ 90.0
+ - MINEMISSION
+
+
+
+ double
+ - 0.0
+ Minimum incidence angle to trim
+
+ Pixels which have an incidence angle less than this value will be
+ trimmed.
+
+ 0.0
+ 180.0
+ - MAXINCIDENCE
+
+
+ double
+ - 90.0
+ Maximum incidence angle to trim
+
+ Pixels which have a incidence angle greater than this value will be
+ trimmed.
+
+ 0.0
+ 180.0
+ - MININCIDENCE
+
+
+
+
+
+ boolean
+ - False
+
+ Use DEM instead of ellipsoid for photometric angle calculations
+
+
+ Phase, Emission, and Incidence angles can be calculated from the
+ elipsoid (default) or from the DEM. The difference is that using the
+ DEM the surface roughness is taken into account.
+
+
+
+ boolean
+ - False
+
+ Output only photometry value in output cube.
+
+
+ Only the photometry value will be written to the output cube, not
+ the original image data. Used for testing photometry calculation.
+
+
+
+ boolean
+ - True
+
+ Normalize photometry value before applying to input image.
+
+
+ Set to False to cause the photometry value to not be normalized.
+ Used for testing photometry calculation.
+
+
+
+
+
+
diff --git a/isis/src/lro/apps/lrowacphomap/main.cpp b/isis/src/lro/apps/lrowacphomap/main.cpp
new file mode 100644
index 0000000000..baf13eb3b4
--- /dev/null
+++ b/isis/src/lro/apps/lrowacphomap/main.cpp
@@ -0,0 +1,21 @@
+/** 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 "Isis.h"
+
+#include "Application.h"
+#include "lrowacphomap.h"
+
+using namespace Isis;
+
+void IsisMain() {
+ UserInterface &ui = Application::GetUserInterface();
+ Pvl results = lrowacphomap(ui);
+ for (int resultIndex = 0; resultIndex < results.groups(); resultIndex++) {
+ Application::Log(results.group(resultIndex));
+ }
+}
\ No newline at end of file
diff --git a/isis/tests/FunctionalTestsLrowacphomap.cpp b/isis/tests/FunctionalTestsLrowacphomap.cpp
new file mode 100644
index 0000000000..19a16134a5
--- /dev/null
+++ b/isis/tests/FunctionalTestsLrowacphomap.cpp
@@ -0,0 +1,98 @@
+#include "Fixtures.h"
+#include "Histogram.h"
+#include "Pvl.h"
+#include "PvlGroup.h"
+#include "TestUtilities.h"
+
+#include "lrowacphomap.h"
+
+#include "gtest/gtest.h"
+
+using namespace Isis;
+
+static QString APP_XML = FileName("$ISISROOT/bin/xml/lrowacphomap.xml").expanded();
+
+TEST(Lrowacphomap, FunctionalTestLrowacphomapWithBack) {
+ QTemporaryDir tempDir;
+ ASSERT_TRUE(tempDir.isValid());
+
+ QString outCubeFileName = tempDir.path() + "/outTemp.cub";
+ QString testCubeFileName = "data/lrowacphomap/M1181493219CE.vis.odd.reduced.cub";
+ QString backplaneFileName = "data/lrowacphomap/back.reduced.cub";
+ QString phoPvlFileName = "data/lrowacphomap/hapke_full_reformatted.pvl";
+ QString paramMapFileName = "data/lrowacphomap/1x1_70NS_7b_wbhs_albflt_grid_geirist_tcorrect.reduced.cub";
+
+ QVector args = {"from=" + testCubeFileName + "+1",
+ "to=" + outCubeFileName,
+ "backplane=" + backplaneFileName,
+ "phoa=" + phoPvlFileName,
+ "phop=" + paramMapFileName};
+ UserInterface options(APP_XML, args);
+
+ try {
+ lrowacphomap(options);
+ }
+ catch(IException &e) {
+ FAIL() << "Call to lrowacphomap failed, unable to apply photometric correction to input cube: " << e.what() << std::endl;
+ }
+
+ double expectedAvg = 58.565850201775;
+ double expectedStdDev = 19.336237864721;
+ double expectedMedian = 56.231717465174;
+ double expectedMin = 21.181716918945;
+ double expectedMax = 160.17492675781;
+ double expectedSum = 197132.65177917;
+
+ Cube outCube(outCubeFileName);
+
+ std::unique_ptr hist(outCube.histogram());
+
+ EXPECT_NEAR(hist->Average(), expectedAvg, 0.001);
+ EXPECT_NEAR(hist->StandardDeviation(), expectedStdDev, 0.001);
+ EXPECT_NEAR(hist->Median(), expectedMedian, 0.001);
+ EXPECT_NEAR(hist->Minimum(), expectedMin, 0.001);
+ EXPECT_NEAR(hist->Maximum(), expectedMax, 0.001);
+ EXPECT_NEAR(hist->Sum(), expectedSum, 0.001);
+}
+
+TEST(Lrowacphomap, FunctionalTestLrowacphomapNoBack) {
+ QTemporaryDir tempDir;
+ ASSERT_TRUE(tempDir.isValid());
+
+ QString outCubeFileName = tempDir.path() + "/outTemp.cub";
+ QString testCubeFileName = "data/lrowacphomap/M1181493219CE.vis.odd.reduced.cub";
+ QString phoPvlFileName = "data/lrowacphomap/hapke_full_reformatted.pvl";
+ QString paramMapFileName = "data/lrowacphomap/1x1_70NS_7b_wbhs_albflt_grid_geirist_tcorrect.reduced.cub";
+
+ QVector args = {"from=" + testCubeFileName + "+1",
+ "to=" + outCubeFileName,
+ "phoa=" + phoPvlFileName,
+ "phop=" + paramMapFileName,
+ "usedem=true"};
+ UserInterface options(APP_XML, args);
+
+ try {
+ lrowacphomap(options);
+ }
+ catch(IException &e) {
+ FAIL() << "Call to lrowacphomap failed, unable to apply photometric correction to input cube: " << e.what() << std::endl;
+ }
+
+ double expectedAvg = 58.159470616532;
+ double expectedStdDev = 18.558190342074;
+ double expectedMedian = 56.508963061387;
+ double expectedMin = 23.405038833618;
+ double expectedMax = 155.67340087891;
+ double expectedSum = 195764.77809525;
+
+ Cube outCube(outCubeFileName);
+
+ std::unique_ptr hist(outCube.histogram());
+
+ EXPECT_NEAR(hist->Average(), expectedAvg, 0.001);
+ EXPECT_NEAR(hist->StandardDeviation(), expectedStdDev, 0.001);
+ EXPECT_NEAR(hist->Median(), expectedMedian, 0.001);
+ EXPECT_NEAR(hist->Minimum(), expectedMin, 0.001);
+ EXPECT_NEAR(hist->Maximum(), expectedMax, 0.001);
+ EXPECT_NEAR(hist->Sum(), expectedSum, 0.001);
+}
diff --git a/isis/tests/data/lrowacphomap/1x1_70NS_7b_wbhs_albflt_grid_geirist_tcorrect.reduced.cub b/isis/tests/data/lrowacphomap/1x1_70NS_7b_wbhs_albflt_grid_geirist_tcorrect.reduced.cub
new file mode 100644
index 0000000000000000000000000000000000000000..15f348406e2f7fc6e728091ca984cfe5f63b1cf8
GIT binary patch
literal 96269
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
X4)mZx-_h6zdI|_y_Yp2maSsvz$gI
+ InterframeDelay = 1109.375
+ BeginTemperatureFpa = -15.50
+ MiddleTemperatureFpa = -11.85
+ EndTemperatureFpa = -8.55
+ BeginTemperatureScs = 7.86
+ MiddleTemperatureScs = 8.09
+ EndTemperatureScs = 8.41
+ BackgroundOffset = 56
+ Mode = 0
+ DataFlipped = No
+ ColorOffset = 0
+ Decompanded = Yes
+ Framelets = Odd
+ NumFramelets = 816
+ InstrumentModeId = COLOR
+ End_Group
+
+ Group = Archive
+ DataSetId = LRO-L-LROC-2-EDR-V1.0
+ ProducerId = LRO_LROC_TEAM
+ ProductId = M1181493219CE
+ ProductVersionId = v1.8
+ RationaleDescription = Null
+ OrbitNumber = 25902
+ DataQualityId = 0
+ OriginalProductId = wac00137217
+ End_Group
+
+ Group = BandBin
+ FilterNumber = (3, 4, 5, 6, 7)
+ Center = (415, 566, 604, 643, 689)
+ Width = (36, 20, 20, 23, 39)
+ End_Group
+
+ Group = Kernels
+ NaifIkCode = -85621
+ LeapSecond = $base/kernels/lsk/naif0012.tls
+ TargetAttitudeShape = ($base/kernels/pck/pck00009.tpc,
+ $lro/kernels/pck/moon_080317.tf,
+ $lro/kernels/pck/moon_assoc_me.tf)
+ TargetPosition = (Table,
+ $lro/kernels/tspk/moon_pa_de421_1900-2050.bpc,
+ $lro/kernels/tspk/de421.bsp)
+ InstrumentPointing = (Table,
+ $lro/kernels/ck/lrolc_2015059_2015091_v01.bc,
+ $lro/kernels/ck/moc42r_2015059_2015091_v01.bc,
+ $lro/kernels/fk/lro_frames_2014049_v01.tf)
+ Instrument = $lro/kernels/ik/lro_lroc_v18.ti
+ SpacecraftClock = $lro/kernels/sclk/lro_clkcor_2022124_v00.tsc
+ InstrumentPosition = (Table,
+ $lro/kernels/spk/LRO_ES_31_201503_GRGM900C_L6-
+ 00.bsp)
+ InstrumentAddendum = $lro/kernels/iak/lro_instrumentAddendum_v05.ti
+ ShapeModel = data/lrowacphomap/WAC_GLD100_V1.0_GLOBAL_with-
+ _LOLA_30M_POLE.16bit.reduced.cub
+ InstrumentPositionQuality = Smithed
+ InstrumentPointingQuality = Reconstructed
+ CameraVersion = 3
+ End_Group
+End_Object
+
+Object = Label
+ Bytes = 65536
+End_Object
+
+Object = Table
+ Name = InstrumentPointing
+ StartByte = 222617
+ Bytes = 728832
+ Records = 11388
+ ByteOrder = Lsb
+ TimeDependentFrames = (-85620, -85000, 1)
+ ConstantFrames = (-85621, -85620)
+ ConstantRotation = (0.99982051808596, 0.0014619008152411,
+ -0.018889003688109, -0.0013858576920097,
+ 0.99999088592261, 0.0040382508789192,
+ 0.01889473505452, -0.0040113486148665,
+ 0.99981343163088)
+ CkTableStartTime = 480126019.2168
+ CkTableEndTime = 480126924.4668
+ CkTableOriginalSize = 11425
+ FrameTypeCode = 3
+ Description = "Created by spiceinit"
+ Kernels = ($lro/kernels/ck/lrolc_2015059_2015091_v01.bc,
+ $lro/kernels/ck/moc42r_2015059_2015091_v01.bc,
+ $lro/kernels/fk/lro_frames_2014049_v01.tf)
+
+ Group = Field
+ Name = J2000Q0
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q1
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q2
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q3
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV1
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV2
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV3
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = ET
+ Type = Double
+ Size = 1
+ End_Group
+End_Object
+
+Object = Table
+ Name = InstrumentPosition
+ StartByte = 951449
+ Bytes = 2352
+ Records = 42
+ ByteOrder = Lsb
+ CacheType = HermiteSpline
+ SpkTableStartTime = 480126019.2168
+ SpkTableEndTime = 480126924.4668
+ SpkTableOriginalSize = 11425.0
+ Description = "Created by spiceinit"
+ Kernels = $lro/kernels/spk/LRO_ES_31_201503_GRGM900C_L600.bsp
+
+ Group = Field
+ Name = J2000X
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Y
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Z
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000XV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000YV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000ZV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = ET
+ Type = Double
+ Size = 1
+ End_Group
+End_Object
+
+Object = Table
+ Name = BodyRotation
+ StartByte = 953801
+ Bytes = 128
+ Records = 2
+ ByteOrder = Lsb
+ TimeDependentFrames = (31006, 1)
+ ConstantFrames = (31001, 31007, 31006)
+ ConstantRotation = (0.99999987325471, -3.29285422375571e-04,
+ 3.80869618671387e-04, 3.29286000210947e-04,
+ 0.99999994578431, -1.45444093783627e-06,
+ -3.80869119096078e-04, 1.57985578682691e-06,
+ 0.99999992746811)
+ CkTableStartTime = 480126019.2168
+ CkTableEndTime = 480126924.4668
+ CkTableOriginalSize = 2
+ FrameTypeCode = 6
+ Description = "Created by spiceinit"
+ Kernels = ($lro/kernels/tspk/moon_pa_de421_1900-2050.bpc,
+ $lro/kernels/tspk/de421.bsp,
+ $base/kernels/pck/pck00009.tpc,
+ $lro/kernels/pck/moon_080317.tf,
+ $lro/kernels/pck/moon_assoc_me.tf)
+ SolarLongitude = 167.7630472262
+
+ Group = Field
+ Name = J2000Q0
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q1
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q2
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Q3
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV1
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV2
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = AV3
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = ET
+ Type = Double
+ Size = 1
+ End_Group
+End_Object
+
+Object = Table
+ Name = SunPosition
+ StartByte = 953929
+ Bytes = 112
+ Records = 2
+ ByteOrder = Lsb
+ CacheType = Linear
+ SpkTableStartTime = 480126019.2168
+ SpkTableEndTime = 480126924.4668
+ SpkTableOriginalSize = 2.0
+ Description = "Created by spiceinit"
+ Kernels = ($lro/kernels/tspk/moon_pa_de421_1900-2050.bpc,
+ $lro/kernels/tspk/de421.bsp)
+
+ Group = Field
+ Name = J2000X
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Y
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000Z
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000XV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000YV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = J2000ZV
+ Type = Double
+ Size = 1
+ End_Group
+
+ Group = Field
+ Name = ET
+ Type = Double
+ Size = 1
+ End_Group
+End_Object
+
+Object = History
+ Name = IsisCube
+ StartByte = 991329
+ Bytes = 3857
+End_Object
+
+Object = NaifKeywords
+ BODY_CODE = 301
+ BODY301_RADII = (1737.4, 1737.4, 1737.4)
+ BODY_FRAME_CODE = 31001
+ INS-85621_SWAP_OBSERVER_TARGET = TRUE
+ INS-85621_LIGHTTIME_CORRECTION = NONE
+ INS-85621_LT_SURFACE_CORRECT = TRUE
+ INS-85621_FOCAL_LENGTH = 6.013
+ INS-85621_PIXEL_PITCH = 0.009
+ CLOCK_ET_-85_1/448546751:65313_COMPUTED = 2b2c3443249ebc41
+ INS-85621_FILTER_BANDCENTER_SIZE_COMPUTED = 5
+ INS-85621_FILTER_BANDCENTER = (415, 566, 604, 643, 689)
+ INS-85621_FILTER_OFFSET_SIZE_COMPUTED = 5
+ INS-85621_FILTER_OFFSET = (703, 728, 754, 781, 806)
+ INS-85621_FILTER_BANDID_SIZE_COMPUTED = 5
+ INS-85621_FILTER_BANDID = (3, 4, 5, 6, 7)
+ INS-85631_FOCAL_LENGTH = 5.9983909
+ INS-85631_BORESIGHT_SAMPLE = 509.5696
+ INS-85631_BORESIGHT_LINE = 775.8181
+ INS-85632_FOCAL_LENGTH = 6.0193387
+ INS-85632_BORESIGHT_SAMPLE = 509.5445
+ INS-85632_BORESIGHT_LINE = 775.7541
+ INS-85633_FOCAL_LENGTH = 6.0247535
+ INS-85633_BORESIGHT_SAMPLE = 509.5379
+ INS-85633_BORESIGHT_LINE = 775.7578
+ INS-85634_FOCAL_LENGTH = 6.0308669
+ INS-85634_BORESIGHT_SAMPLE = 509.5297
+ INS-85634_BORESIGHT_LINE = 775.7545
+ INS-85635_FOCAL_LENGTH = 6.0377111
+ INS-85635_BORESIGHT_SAMPLE = 509.5314
+ INS-85635_BORESIGHT_LINE = 775.7604
+ INS-85621_COLOR_SAMPLE_OFFSET = 160
+ INS-85621_TRANSX = (0.0, 0.0, -0.009)
+ INS-85621_TRANSY = (0.0, 0.009, 0.0)
+ INS-85621_ITRANSS = (0.0, 0.0, 111.11111111111)
+ INS-85621_ITRANSL = (0.0, -111.11111111111, 0.0)
+ INS-85621_OD_K = (-0.0099, -5.0e-04,
+ 4.65009260657478e-310)
+ INS-85631_TRANSX = (0.0, 5.90365231e-07,
+ -0.008999999980637)
+ INS-85631_TRANSY = (0.0, 0.008999999980637,
+ 5.90365231e-07)
+ INS-85631_ITRANSS = (0.0, 0.0072884596481,
+ 111.11111087206)
+ INS-85631_ITRANSL = (0.0, -111.11111087206,
+ 0.0072884596481)
+ INS-85631_OD_K = (0.011386711675385,
+ 1.4704581347987e-04,
+ 5.03800502043867e-06)
+ INS-85632_TRANSX = (0.0, 2.16143494e-07,
+ -0.008999999997405)
+ INS-85632_TRANSY = (0.0, 0.008999999997405,
+ 2.16143494e-07)
+ INS-85632_ITRANSS = (0.0, 0.0026684381921,
+ 111.11111107907)
+ INS-85632_ITRANSL = (0.0, -111.11111107907,
+ 0.0026684381921)
+ INS-85632_OD_K = (0.01133013728411,
+ 1.47788496863675e-04,
+ 4.87090732732635e-06)
+ INS-85633_TRANSX = (0.0, -8.5741007e-08,
+ -0.008999999999592)
+ INS-85633_TRANSY = (0.0, 0.008999999999592,
+ -8.5741007e-08)
+ INS-85633_ITRANSS = (0.0, -0.0010585309475,
+ 111.11111110607)
+ INS-85633_ITRANSL = (0.0, -111.11111110607,
+ -0.0010585309475)
+ INS-85633_OD_K = (0.011302901258766,
+ 1.47434788741622e-04,
+ 4.92841571946775e-06)
+ INS-85634_TRANSX = (0.0, -2.600831484e-06,
+ -0.008999999624204)
+ INS-85634_TRANSY = (0.0, 0.008999999624204,
+ -2.600831484e-06)
+ INS-85634_ITRANSS = (0.0, -0.0321090306666,
+ 111.11110647166)
+ INS-85634_ITRANSL = (0.0, -111.11110647166,
+ -0.0321090306666)
+ INS-85634_OD_K = (0.011310945216636,
+ 1.44463288593614e-04,
+ 4.88754251291127e-06)
+ INS-85635_TRANSX = (0.0, -6.68455587e-07,
+ -0.008999999975176)
+ INS-85635_TRANSY = (0.0, 0.008999999975176,
+ -6.68455587e-07)
+ INS-85635_ITRANSS = (0.0, -0.0082525381149,
+ 111.11111080464)
+ INS-85635_ITRANSL = (0.0, -111.11111080464,
+ -0.0082525381149)
+ INS-85635_OD_K = (0.011256422654675,
+ 1.46609439724495e-04,
+ 4.80869164345756e-06)
+End_Object
+
+Object = OriginalLabel
+ Name = IsisCube
+ StartByte = 954041
+ Bytes = 37288
+End_Object
+End
+
CUNCEjCMCMfCdC9iC9qCqC]C .CrzC5C`Cr,C佢C9IC pC}CUCC9{CsC zLCUIC%QCaC lCmCyyC CCUC9لCC9C C>CCǑCrC8C9C DCUQCOCraCrvQC(zCdCrdCU#yCUށCdzrC}CUCאCrC9fCMCUѹCUCUCCDCǻ>CUJCKC\CU`C9[CǟqC/tCŁCC%C )CC9_C9CC9kCrC CCCCU@CSC9HHC0NC-[C9kC CUCC䏎C~CǝC