From 60e884f2b4f2d49895302d21879a780f75fbd9e1 Mon Sep 17 00:00:00 2001 From: kledmundson <6842706+kledmundson@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:58:13 -0700 Subject: [PATCH] Algebra has been refactored to be callable; old Makefile tests have been converted to gtests and removed. Addresses #5594. (#5597) --- CHANGELOG.md | 1 + isis/src/base/apps/algebra/algebra.cpp | 178 ++++++ isis/src/base/apps/algebra/algebra.h | 19 + isis/src/base/apps/algebra/algebra.xml | 3 + isis/src/base/apps/algebra/main.cpp | 149 +---- isis/src/base/apps/algebra/tsts/Makefile | 4 - isis/src/base/apps/algebra/tsts/add/Makefile | 12 - .../base/apps/algebra/tsts/divide/Makefile | 12 - .../base/apps/algebra/tsts/multiply/Makefile | 12 - .../base/apps/algebra/tsts/subtract/Makefile | 11 - .../src/base/apps/algebra/tsts/unary/Makefile | 10 - isis/tests/FunctionalTestsAlgebra.cpp | 522 ++++++++++++++++++ 12 files changed, 735 insertions(+), 198 deletions(-) create mode 100644 isis/src/base/apps/algebra/algebra.cpp create mode 100644 isis/src/base/apps/algebra/algebra.h delete mode 100644 isis/src/base/apps/algebra/tsts/Makefile delete mode 100644 isis/src/base/apps/algebra/tsts/add/Makefile delete mode 100644 isis/src/base/apps/algebra/tsts/divide/Makefile delete mode 100644 isis/src/base/apps/algebra/tsts/multiply/Makefile delete mode 100644 isis/src/base/apps/algebra/tsts/subtract/Makefile delete mode 100644 isis/src/base/apps/algebra/tsts/unary/Makefile create mode 100644 isis/tests/FunctionalTestsAlgebra.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e339fb97..e5b28550d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ release. - Added backplane options for SunIllumination and SurfaceObliqueDetectorResolution to phocube [#5467](https://github.com/DOI-USGS/ISIS3/issues/5467) ### Changed +- Algebra has been refactored to be callable; old Makefile tests have been removed and replaced by gtests. Issue: [#5594](https://github.com/USGS-Astrogeology/ISIS3/issues/5594) - Photrim has been refactored to be callable; old Makefile tests have been removed and replaced by gtests. Issue: [#5581](https://github.com/USGS-Astrogeology/ISIS3/issues/5581) - Bandtrim has been refactored to be callable; old Makefile tests have been removed and replaced by gtests. Issue: [#5571](https://github.com/USGS-Astrogeology/ISIS3/issues/5571) - Modified kaguyasp2isis to work with new (detached) data [#5436](https://github.com/DOI-USGS/ISIS3/issues/5436) diff --git a/isis/src/base/apps/algebra/algebra.cpp b/isis/src/base/apps/algebra/algebra.cpp new file mode 100644 index 0000000000..0ad5ab854d --- /dev/null +++ b/isis/src/base/apps/algebra/algebra.cpp @@ -0,0 +1,178 @@ +/** 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 "algebra.h" + +#include "Cube.h" +#include "FileName.h" +#include "ProcessByLine.h" +#include "SpecialPixel.h" + +namespace Isis { + + /* + * Algebra + * + * This program performs simple algebra on either one or two + * cubes. The two cubes may be added, subtracted, multiplied or divided. + * + * The following equations are used: + * UNARY: out = (A * from1) + C + * ADD: out = ((from1 - D) * A) + ((from2 - E) * B) + C + * SUBTRACT: out = ((from1 - D) * A) - ((from2 - E) * B) + C + * MULTIPLY: out = ((from1 - D) * A) * ((from2 - E) * B) + C + * DIVIDE: out = ((from1 - D) * A) / ((from2 - E) * B) + C + * + * The FROM2 cube must have either one band or the same number of bands + * as the FROM cube. If the FROM2 cube has one band, then the algebraic + * formula will be applied to all bands in FROM using that single band + * in FROM2. If FROM2 is a multi-band cube, the algebra will be performed + * between corresponding bands from FROM and FROM2. + * + * @param ui UserInterface object containing parameters + */ + void algebra(UserInterface &ui) { + Cube* icube1 = new Cube(); + icube1->open(ui.GetCubeName("FROM")); + + Cube* icube2 = nullptr; + if(ui.WasEntered("FROM2")) { + icube2 = new Cube(); + icube2->open(ui.GetCubeName("FROM2")); + } + + algebra(icube1, ui, icube2); + } + + + /* + * Algebra + * + * This program performs simple algebra on either one or two + * cubes. The two cubes may be added, subtracted, multiplied or divided. + * + * The following equations are used: + * UNARY: out = (A * from1) + C + * ADD: out = ((from1 - D) * A) + ((from2 - E) * B) + C + * SUBTRACT: out = ((from1 - D) * A) - ((from2 - E) * B) + C + * MULTIPLY: out = ((from1 - D) * A) * ((from2 - E) * B) + C + * DIVIDE: out = ((from1 - D) * A) / ((from2 - E) * B) + C + * + * The FROM2 cube must have either one band or the same number of bands + * as the FROM cube. If the FROM2 cube has one band, then the algebraic + * formula will be applied to all bands in FROM using that single band + * in FROM2. If FROM2 is a multi-band cube, the algebra will be performed + * between corresponding bands from FROM and FROM2. + * + * @param icube1 Cube* input cube1 + * @param ui UserInterface object containing parameters + * @param icube2 Cube* input cube2; optional second input cube + */ + void algebra(Cube* icube1, UserInterface &ui, Cube* icube2) { + + // Processing by line + ProcessByLine p; + + // Set input cubes and attributes into ProcessByLine p + CubeAttributeInput inatts1 = ui.GetInputAttribute("FROM"); + CubeAttributeInput inatts2; + p.SetInputCube(icube1->fileName(), inatts1); + if(icube2 != nullptr) { + inatts2 = ui.GetInputAttribute("FROM2"); + p.SetInputCube(icube2->fileName(), inatts2); + } + + // Set output cube and attributes into ProcessByLine p + QString outCubeFname = ui.GetCubeName("TO"); + CubeAttributeOutput &outatts = ui.GetOutputAttribute("TO"); + p.SetOutputCube(outCubeFname, outatts); + + // Get the coefficients + double Isisa = ui.GetDouble("A"); + double Isisb = ui.GetDouble("B"); + double Isisc = ui.GetDouble("C"); + double Isisd = ui.GetDouble("D"); + double Isise = ui.GetDouble("E"); + + QString op = ui.GetString("OPERATOR"); + + //***************************************** + // Lambda functions to perform operations * + //***************************************** + + // operatorProcess for add, subtract, multiply, divide + auto operatorProcess = [&](std::vector &in, std::vector &out)->void { + Buffer &inp1 = *in[0]; + Buffer &inp2 = *in[1]; + Buffer &outp = *out[0]; + + // Loop for each pixel in the line + // Special pixel propagation: + // 1) special pixels in inp1 propagate to output cube unchanged + // 2) if inp1 is not special and inp2 is, the output pixel is set to Null + for(int i = 0; i < inp1.size(); i++) { + if(IsSpecial(inp1[i])) { + outp[i] = inp1[i]; + } + else if(IsSpecial(inp2[i])) { + outp[i] = NULL8; + } + else { + double operand1 = (inp1[i] - Isisd) * Isisa; + double operand2 = (inp2[i] - Isise) * Isisb; + if(op == "ADD") { + outp[i] = (operand1 + operand2) + Isisc; + } + if(op == "SUBTRACT") { + outp[i] = (operand1 - operand2) + Isisc; + } + if(op == "MULTIPLY") { + outp[i] = (operand1 * operand2) + Isisc; + } + if(op == "DIVIDE") { + if((inp2[i] - Isise) * Isisb == 0.0) { + outp[i] = NULL8; + } + else { + outp[i] = (operand1 / operand2) + Isisc; + } + } + } + } + }; + + // Unary process + auto unaryProcess = [&](Buffer &in, Buffer &out)->void { + // Loop for each pixel in the line. + // Special pixels propagate directly to output + for(int i = 0; i < in.size(); i++) { + if(IsSpecial(in[i])) { + out[i] = in[i]; + } + else { + out[i] = in[i] * Isisa + Isisc; + } + } + }; + + //***************************************** + // End Lambda functions * + //***************************************** + + // Start processing based on the operator + if(op == "UNARY") { + p.ProcessCube(unaryProcess); + } + else { + p.ProcessCubes(operatorProcess); // add, subtract, multiply, divide + } + + p.EndProcess(); + } +} + diff --git a/isis/src/base/apps/algebra/algebra.h b/isis/src/base/apps/algebra/algebra.h new file mode 100644 index 0000000000..43d288269d --- /dev/null +++ b/isis/src/base/apps/algebra/algebra.h @@ -0,0 +1,19 @@ +/** 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 */ + +#ifndef algebra_h +#define algebra_h + +#include "UserInterface.h" + +namespace Isis{ + extern void algebra(UserInterface &ui); + extern void algebra(Cube* icube1, UserInterface &ui, Cube* icube2=nullptr); +} + +#endif diff --git a/isis/src/base/apps/algebra/algebra.xml b/isis/src/base/apps/algebra/algebra.xml index f81fbc30b1..9b6679a81d 100644 --- a/isis/src/base/apps/algebra/algebra.xml +++ b/isis/src/base/apps/algebra/algebra.xml @@ -87,6 +87,9 @@ Updated to use the new ProcessByLine API. This program now takes advantage of multiple global processing threads. + + Converted to callable app and converted Makefile tests to gtests. + diff --git a/isis/src/base/apps/algebra/main.cpp b/isis/src/base/apps/algebra/main.cpp index 26a27eeaea..f4644b40dd 100644 --- a/isis/src/base/apps/algebra/main.cpp +++ b/isis/src/base/apps/algebra/main.cpp @@ -1,146 +1,21 @@ -#include "Isis.h" -#include "ProcessByBrick.h" -#include "ProcessByLine.h" -#include "SpecialPixel.h" -#include "IException.h" - -using namespace std; -using namespace Isis; +/** This is free and unencumbered software released into the public domain. -void add(vector &in, - vector &out); -void sub(vector &in, - vector &out); -void mult(vector &in, - vector &out); -void divide(vector &in, - vector &out); -void unaryFunction(Buffer &in, Buffer &out); - -double Isisa, Isisb, Isisc, Isisd, Isise; - -void IsisMain() { - std::cout << "algebra - got to main...\n"; +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. **/ - // We will be processing by line - ProcessByLine p; - - // Setup the input and output files - UserInterface &ui = Application::GetUserInterface(); - // Setup the input and output cubes - p.SetInputCube("FROM"); - if(ui.WasEntered("FROM2")) p.SetInputCube("FROM2"); - p.SetOutputCube("TO"); - - // Get the coefficients - Isisa = ui.GetDouble("A"); - Isisb = ui.GetDouble("B"); - Isisc = ui.GetDouble("C"); - Isisd = ui.GetDouble("D"); - Isise = ui.GetDouble("E"); - - // Start the processing based on the operator - QString op = ui.GetString("OPERATOR"); - if(op == "ADD") p.ProcessCubes(&add); - if(op == "SUBTRACT") p.ProcessCubes(&sub); - if(op == "MULTIPLY") p.ProcessCubes(&mult); - if(op == "DIVIDE") p.ProcessCubes(÷); - if(op == "UNARY") p.ProcessCube(&unaryFunction); -} +/* SPDX-License-Identifier: CC0-1.0 */ -// Add routine -void add(vector &in, vector &out) { - Buffer &inp1 = *in[0]; - Buffer &inp2 = *in[1]; - Buffer &outp = *out[0]; - // Loop for each pixel in the line. - for(int i = 0; i < inp1.size(); i++) { - if(IsSpecial(inp1[i])) { - outp[i] = inp1[i]; - } - else if(IsSpecial(inp2[i])) { - outp[i] = NULL8; - } - else { - outp[i] = ((inp1[i] - Isisd) * Isisa) + ((inp2[i] - Isise) * Isisb) + Isisc; - } - } -} - -// Sub routine -void sub(vector &in, vector &out) { - Buffer &inp1 = *in[0]; - Buffer &inp2 = *in[1]; - Buffer &outp = *out[0]; - - // Loop for each pixel in the line. - for(int i = 0; i < inp1.size(); i++) { - if(IsSpecial(inp1[i])) { - outp[i] = inp1[i]; - } - else if(IsSpecial(inp2[i])) { - outp[i] = NULL8; - } - else { - outp[i] = ((inp1[i] - Isisd) * Isisa) - ((inp2[i] - Isise) * Isisb) + Isisc; - } - } -} +#include "Isis.h" -// Sub routine -void mult(vector &in, vector &out) { - Buffer &inp1 = *in[0]; - Buffer &inp2 = *in[1]; - Buffer &outp = *out[0]; +#include "algebra.h" - // Loop for each pixel in the line. - for(int i = 0; i < inp1.size(); i++) { - if(IsSpecial(inp1[i])) { - outp[i] = inp1[i]; - } - else if(IsSpecial(inp2[i])) { - outp[i] = NULL8; - } - else { - outp[i] = ((inp1[i] - Isisd) * Isisa) * ((inp2[i] - Isise) * Isisb) + Isisc; - } - } -} +#include "Application.h" -// Div routine -void divide(vector &in, vector &out) { - Buffer &inp1 = *in[0]; - Buffer &inp2 = *in[1]; - Buffer &outp = *out[0]; +using namespace Isis; - // Loop for each pixel in the line. - for(int i = 0; i < inp1.size(); i++) { - if(IsSpecial(inp1[i])) { - outp[i] = inp1[i]; - } - else if(IsSpecial(inp2[i])) { - outp[i] = NULL8; - } - else { - if((inp2[i] - Isise) * Isisb == 0.0) { - outp[i] = NULL8; - } - else { - outp[i] = ((inp1[i] - Isisd) * Isisa) / ((inp2[i] - Isise) * Isisb) + Isisc; - } - } - } +void IsisMain() { + UserInterface &ui = Application::GetUserInterface(); + algebra(ui); } -// Unary routine -void unaryFunction(Buffer &in, Buffer &out) { - // Loop for each pixel in the line. - for(int i = 0; i < in.size(); i++) { - if(IsSpecial(in[i])) { - out[i] = in[i]; - } - else { - out[i] = in[i] * Isisa + Isisc; - } - } -} diff --git a/isis/src/base/apps/algebra/tsts/Makefile b/isis/src/base/apps/algebra/tsts/Makefile deleted file mode 100644 index 46d84c74c2..0000000000 --- a/isis/src/base/apps/algebra/tsts/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -BLANKS = "%-6s" -LENGTH = "%-40s" - -include $(ISISROOT)/make/isismake.tststree diff --git a/isis/src/base/apps/algebra/tsts/add/Makefile b/isis/src/base/apps/algebra/tsts/add/Makefile deleted file mode 100644 index ec62a9df70..0000000000 --- a/isis/src/base/apps/algebra/tsts/add/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -APPNAME = algebra - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/isisTruth.cub+1 \ - from2=$(INPUT)/isisTruth.cub+2 \ - to=$(OUTPUT)/algebraTruth2.cub \ - operator=add \ - a=1 \ - b=1 \ - c=0 > /dev/null; diff --git a/isis/src/base/apps/algebra/tsts/divide/Makefile b/isis/src/base/apps/algebra/tsts/divide/Makefile deleted file mode 100644 index 78eb2f4ea6..0000000000 --- a/isis/src/base/apps/algebra/tsts/divide/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -APPNAME = algebra - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/isisTruth.cub+1 \ - from2=$(INPUT)/isisTruth.cub+2 \ - to=$(OUTPUT)/algebraTruth5.cub \ - operator=divide \ - a=1 \ - b=1 \ - c=0 > /dev/null; diff --git a/isis/src/base/apps/algebra/tsts/multiply/Makefile b/isis/src/base/apps/algebra/tsts/multiply/Makefile deleted file mode 100644 index 14ae886b13..0000000000 --- a/isis/src/base/apps/algebra/tsts/multiply/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -APPNAME = algebra - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/isisTruth.cub+1 \ - from2=$(INPUT)/isisTruth.cub+2 \ - to=$(OUTPUT)/algebraTruth4.cub \ - operator=multiply \ - a=1 \ - b=1 \ - c=0 > /dev/null; diff --git a/isis/src/base/apps/algebra/tsts/subtract/Makefile b/isis/src/base/apps/algebra/tsts/subtract/Makefile deleted file mode 100644 index a6df541d8e..0000000000 --- a/isis/src/base/apps/algebra/tsts/subtract/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -APPNAME = algebra - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/isisTruth.cub+1 \ - from2=$(INPUT)/isisTruth.cub+2 \ - to=$(OUTPUT)/algebraTruth3.cub \ - a=1 \ - b=1 \ - c=0 > /dev/null; diff --git a/isis/src/base/apps/algebra/tsts/unary/Makefile b/isis/src/base/apps/algebra/tsts/unary/Makefile deleted file mode 100644 index fa0e199043..0000000000 --- a/isis/src/base/apps/algebra/tsts/unary/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -APPNAME = algebra - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/isisTruth.cub+1 \ - to= $(OUTPUT)/algebraTruth1.cub \ - operator=unary \ - a=1 \ - c=0 > /dev/null; diff --git a/isis/tests/FunctionalTestsAlgebra.cpp b/isis/tests/FunctionalTestsAlgebra.cpp new file mode 100644 index 0000000000..8e8f4cbbf4 --- /dev/null +++ b/isis/tests/FunctionalTestsAlgebra.cpp @@ -0,0 +1,522 @@ +#include "algebra.h" +#include "Brick.h" +#include "CameraFixtures.h" +#include "Histogram.h" +#include "LineManager.h" + +#include "gtest/gtest.h" + +using namespace Isis; + +static QString APP_XML = FileName("$ISISROOT/bin/xml/algebra.xml").expanded(); + +/** + * class AlgebraCube + * + * Reimplements DefaultCube::resizeCube method to force the use + * of Real dns to properly test propagation of special pixels + * to the output cube. + */ +class AlgebraCube : public DefaultCube { + protected: + + void resizeCube(int samples, int lines, int bands) { + label = Pvl(); + PvlObject &isisCube = testCube->label()->findObject("IsisCube"); + label.addObject(isisCube); + + PvlGroup &dim = label.findObject("IsisCube").findObject("Core").findGroup("Dimensions"); + dim.findKeyword("Samples").setValue(QString::number(samples)); + dim.findKeyword("Lines").setValue(QString::number(lines)); + dim.findKeyword("Bands").setValue(QString::number(bands)); + + // force Real dns + PvlObject &core = label.findObject("IsisCube").findObject("Core"); + PvlGroup &pixels = core.findGroup("Pixels"); + pixels.findKeyword("Type").setValue("Real"); + + delete testCube; + testCube = new Cube(); + testCube->fromIsd(tempDir.path() + "/default.cub", label, isd, "rw"); + + LineManager line(*testCube); + int pixelValue = 1; + for(int band = 1; band <= bands; band++) { + for (int i = 1; i <= testCube->lineCount(); i++) { + line.SetLine(i, band); + for (int j = 0; j < line.size(); j++) { + line[j] = (double) (pixelValue % 255); + pixelValue++; + } + testCube->write(line); + } + } + + // set 1st three diagonal pixels of band 1 to special pixels Null, Lrs, and Lis + // set last two diagonal pixels of band 2 to special pixels Hrs and His + + Brick b(1, 1, 1, testCube->pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + b[0] = Isis::Null; + testCube->write(b); + + b.SetBasePosition(2, 2, 1); + b[0] = Isis::Lrs; + testCube->write(b); + + b.SetBasePosition(3, 3, 1); + b[0] = Isis::Lis; + testCube->write(b); + + b.SetBasePosition(4, 4, 2); + b[0] = Isis::Hrs; + testCube->write(b); + + b.SetBasePosition(5, 5, 2); + b[0] = Isis::His; + testCube->write(b); + } +}; + + +/** + * FunctionalTestAlgebraAdd + * + * Pixel by pixel addition of bands 1 and 2 of input cube. + * + * INPUT: testCube from DefaultCube fixture with 2 bands. + * a=1 (multiplicative constant for 1st input cube) + * b=1 (multiplicative constant for 2nd input cube) + * c=0 (additive constant for entire equation) + * d=0 (additive constant for 1st input cube) + * e=0 (additive constant for 2nd input cube) + * + * Band 1 Band 2 + * + * | N | 2 | 3 | 4 | 5 | | 26| 27| 28| 29| 30| + * | 6 |Lrs| 8 | 9 | 10| | 31| 32| 33| 34| 35| + * | 11| 12|Lis| 14| 15| | 36| 37| 38| 39| 40| + * | 16| 17| 18| 19| 20| | 41| 42| 43|Hrs| 45| + * | 21| 22| 23| 24| 25| | 46| 47| 48| 49|His| + * + * OUTPUT: algebraAddOut.cub + * + * | N | 29| 31| 33| 35| + * | 37|Lrs| 41| 43| 45| + * | 47| 49|Lis| 53| 55| + * | 57| 59| 61| N | 65| + * | 67| 69| 71| 73| N | + */ +TEST_F(AlgebraCube, FunctionalTestAlgebraAdd) { + + // reduce test cube size, create two bands + resizeCube(5, 5, 2); + + // close and reopen test cube to ensure dn buffer is available + testCube->reopen("r"); + + // run algebra + QVector args = {"from=" + testCube->fileName() + "+1", + "from2=" + testCube->fileName() + "+2", + "to=" + tempDir.path() + "/algebraAddOut.cub", + "operator=add", + "a=1", + "b=1", + "c=0"}; + UserInterface ui(APP_XML, args); + + try { + algebra(testCube, ui, testCube); + } + catch(IException &e) { + FAIL() << e.toString().toStdString().c_str() << std::endl; + } + + // Open output cube + Cube outCube(tempDir.path() + "/algebraAddOut.cub"); + + // validate histogram statistics in output cube + std::unique_ptr hist (outCube.histogram(1)); + + EXPECT_EQ(hist->ValidPixels(), 20); + EXPECT_EQ(hist->Average(), 51); + EXPECT_EQ(hist->Sum(), 1020); + + // validate propagation of special pixels + Brick b(1, 1, 1, outCube.pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(2, 2, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lrs"); + + b.SetBasePosition(3, 3, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lis"); + + b.SetBasePosition(4, 4, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(5, 5, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + outCube.close(); +} + + +/** + * FunctionalTestAlgebraSubtract + * + * Pixel by pixel subtraction of band 1 from band 2 of input cube. + * + * INPUT: testCube from DefaultCube fixture with 2 bands. + * a=1 (multiplicative constant for 1st input cube) + * b=1 (multiplicative constant for 2nd input cube) + * c=0 (additive constant for entire equation) + * d=0 (additive constant for 1st input cube) + * e=0 (additive constant for 2nd input cube) + * + * Band 1 Band 2 + * + * | N | 2 | 3 | 4 | 5 | | 26| 27| 28| 29| 30| + * | 6 |Lrs| 8 | 9 | 10| | 31| 32| 33| 34| 35| + * | 11| 12|Lis| 14| 15| | 36| 37| 38| 39| 40| + * | 16| 17| 18| 19| 20| | 41| 42| 43|Hrs| 45| + * | 21| 22| 23| 24| 25| | 46| 47| 48| 49|His| + * + * OUTPUT: algebraSubtractOut.cub + * + * | N | 25| 25| 25| 25| + * | 25| N| 25| 25| 25| + * | 25| 25| N| 25| 25| + * | 25| 25| 25|Hrs| 25| + * | 25| 25| 25| 25|His| + */ +TEST_F(AlgebraCube, FunctionalTestAlgebraSubtract) { + + // reduce test cube size, create two bands + resizeCube(5, 5, 2); + + // close and reopen test cube to ensure dn buffer is available + testCube->reopen("r"); + + // run algebra + QVector args = {"from=" + testCube->fileName() + "+2", + "from2=" + testCube->fileName() + "+1", + "to=" + tempDir.path() + "/algebraSubtractOut.cub", + "operator=subtract", + "a=1", + "b=1", + "c=0"}; + UserInterface ui(APP_XML, args); + + try { + algebra(testCube, ui, testCube); + } + catch(IException &e) { + FAIL() << e.toString().toStdString().c_str() << std::endl; + } + + // Open output cube + Cube outCube(tempDir.path() + "/algebraSubtractOut.cub"); + + // validate histogram statistics in output cube + std::unique_ptr hist (outCube.histogram(1)); + + EXPECT_EQ(hist->ValidPixels(), 20); + EXPECT_EQ(hist->Average(), 25); + EXPECT_EQ(hist->Sum(), 500); + + // validate propagation of special pixels + Brick b(1, 1, 1, outCube.pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(2, 2, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(3, 3, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(4, 4, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Hrs"); + + b.SetBasePosition(5, 5, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "His"); + + outCube.close(); +} + + +/** + * FunctionalTestAlgebraMultiply + * + * Pixel by pixel multiplication of band 1 from band 2 of input cube. + * + * INPUT: testCube from DefaultCube fixture with 2 bands. + * a=1 (multiplicative constant for 1st input cube) + * b=1 (multiplicative constant for 2nd input cube) + * c=0 (additive constant for entire equation) + * d=0 (additive constant for 1st input cube) + * e=0 (additive constant for 2nd input cube) + * + * Band 1 Band 2 + * + * | N | 2 | 3 | 4 | 5 | | 26| 27| 28| 29| 30| + * | 6 |Lrs| 8 | 9 | 10| | 31| 32| 33| 34| 35| + * | 11| 12|Lis| 14| 15| | 36| 37| 38| 39| 40| + * | 16| 17| 18| 19| 20| | 41| 42| 43|Hrs| 45| + * | 21| 22| 23| 24| 25| | 46| 47| 48| 49|His| + * + * OUTPUT: algebraMultiplyOut.cub + * + * | N| 54| 84| 116| 150| + * | 186| Lrs| 264| 306| 350| + * | 396| 444| Lis| 546| 600| + * | 656| 714| 774| N| 900| + * | 966|1034|1104|1176| N| + */ +TEST_F(AlgebraCube, FunctionalTestAlgebraMultiply) { + + // reduce test cube size, create two bands + resizeCube(5, 5, 2); + + // close and reopen test cube to ensure dn buffer is available + testCube->reopen("r"); + + // run algebra + QVector args = {"from=" + testCube->fileName() + "+1", + "from2=" + testCube->fileName() + "+2", + "to=" + tempDir.path() + "/algebraMultiplyOut.cub", + "operator=multiply", + "a=1", + "b=1", + "c=0"}; + UserInterface ui(APP_XML, args); + + try { + algebra(testCube, ui, testCube); + } + catch(IException &e) { + FAIL() << e.toString().toStdString().c_str() << std::endl; + } + + // Open output cube + Cube outCube(tempDir.path() + "/algebraMultiplyOut.cub"); + + // validate histogram statistics in output cube + std::unique_ptr hist (outCube.histogram(1)); + + EXPECT_EQ(hist->ValidPixels(), 20); + EXPECT_EQ(hist->Average(), 541); + EXPECT_EQ(hist->Sum(), 10820); + + // validate propagation of special pixels + Brick b(1, 1, 1, outCube.pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(2, 2, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lrs"); + + b.SetBasePosition(3, 3, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lis"); + + b.SetBasePosition(4, 4, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(5, 5, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + outCube.close(); +} + + +/** + * FunctionalTestAlgebraDivide + * + * Pixel by pixel division band 1 / band 2 of input cube. + * + * INPUT: testCube from DefaultCube fixture with 2 bands. + * a=1 (multiplicative constant for 1st input cube) + * b=1 (multiplicative constant for 2nd input cube) + * c=0 (additive constant for entire equation) + * d=0 (additive constant for 1st input cube) + * e=0 (additive constant for 2nd input cube) + * + * Band 1 Band 2 + * + * | N | 2 | 3 | 4 | 5 | | 26| 27| 28| 29| 30| + * | 6 |Lrs| 8 | 9 | 10| | 31| 32| 33| 34| 35| + * | 11| 12|Lis| 14| 15| | 36| 37| 38| 39| 40| + * | 16| 17| 18| 19| 20| | 41| 42| 43|Hrs| 45| + * | 21| 22| 23| 24| 25| | 46| 47| 48| 49|His| + * + * OUTPUT: algebraDivideOut.cub + * + * | N|.074|.107|.137|.166| Values shown truncated at 3 decimal places + * |.193| Lrs|.242|.264|.285| + * |.305|.324| Lis|.358|.375| + * |.390|.404|.418| N|.444| + * |.456|.468|.479|.489| N| + */ + TEST_F(AlgebraCube, FunctionalTestAlgebraDivide) { + + // reduce test cube size, create two bands + resizeCube(5, 5, 2); + + // close and reopen test cube to ensure dn buffer is available + testCube->reopen("r"); + + // run algebra + QVector args = {"from=" + testCube->fileName() + "+1", + "from2=" + testCube->fileName() + "+2", + "to=" + tempDir.path() + "/algebraDivideOut.cub", + "operator=divide", + "a=1", + "b=1", + "c=0"}; + UserInterface ui(APP_XML, args); + + try { + algebra(testCube, ui, testCube); + } + catch(IException &e) { + FAIL() << e.toString().toStdString().c_str() << std::endl; + } + + // Open output cube + Cube outCube(tempDir.path() + "/algebraDivideOut.cub"); + + // validate histogram statistics in output cube + std::unique_ptr hist (outCube.histogram(1)); + + EXPECT_EQ(hist->ValidPixels(), 20); + EXPECT_NEAR(hist->Average(), 0.319384, 0.000001); + EXPECT_NEAR(hist->Sum(), 6.387686, 0.000001); + + // validate propagation of special pixels + Brick b(1, 1, 1, outCube.pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(2, 2, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lrs"); + + b.SetBasePosition(3, 3, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lis"); + + b.SetBasePosition(4, 4, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(5, 5, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + outCube.close(); +} + + +/** + * FunctionalTestAlgebraUnary + * + * Unary processing of band 1 (a * from1) + c). + * + * INPUT: testCube from DefaultCube fixture with 2 bands. + * a=1 (multiplicative constant for 1st input cube) + * b=1 (multiplicative constant for 2nd input cube) + * c=0 (additive constant for entire equation) + * d=0 (additive constant for 1st input cube) + * e=0 (additive constant for 2nd input cube) + * + * Band 1 + * + * | N | 2 | 3 | 4 | 5 | + * | 6 |Lrs| 8 | 9 | 10| + * | 11| 12|Lis| 14| 15| + * | 16| 17| 18| 19| 20| + * | 21| 22| 23| 24| 25| + * + * OUTPUT: algebraUnaryOut.cub (identical to input band 1) + * + * | N | 2 | 3 | 4 | 5 | + * | 6 |Lrs| 8 | 9 | 10| + * | 11| 12|Lis| 14| 15| + * | 16| 17| 18| 19| 20| + * | 21| 22| 23| 24| 25| + */ + TEST_F(AlgebraCube, FunctionalTestAlgebraUnary) { + + // reduce test cube size, create two bands + resizeCube(5, 5, 1); + + // close and reopen test cube to ensure dn buffer is available + testCube->reopen("r"); + + // run algebra + QVector args = {"from=" + testCube->fileName() + "+1", + "to=" + tempDir.path() + "/algebraUnaryOut.cub", + "operator=unary", + "a=1", + "c=0"}; + UserInterface ui(APP_XML, args); + + try { + algebra(testCube, ui); + } + catch(IException &e) { + FAIL() << e.toString().toStdString().c_str() << std::endl; + } + + // Open output cube + Cube outCube(tempDir.path() + "/algebraUnaryOut.cub"); + + // validate histogram statistics in output cube + std::unique_ptr hist (outCube.histogram(1)); + + EXPECT_EQ(hist->ValidPixels(), 22); + EXPECT_NEAR(hist->Average(), 13.818181, 0.000001); + EXPECT_EQ(hist->Sum(), 304); + + // validate propagation of special pixels + Brick b(1, 1, 1, outCube.pixelType()); // create buffer of size 1 pixel + + b.SetBasePosition(1, 1, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Null"); + + b.SetBasePosition(2, 2, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lrs"); + + b.SetBasePosition(3, 3, 1); + outCube.read(b); + EXPECT_EQ(PixelToString(b[0]), "Lis"); + + outCube.close(); +} + +