diff --git a/isis/src/base/apps/bandnorm/bandnorm.cpp b/isis/src/base/apps/bandnorm/bandnorm.cpp new file mode 100644 index 0000000000..d237f08ebd --- /dev/null +++ b/isis/src/base/apps/bandnorm/bandnorm.cpp @@ -0,0 +1,173 @@ +// system include files go first +#include +#include +#include +#include +#include + +// Isis specific include files go next +#include "ProcessByLine.h" +#include "SpecialPixel.h" +#include "IException.h" +#include "Pvl.h" +#include "TextFile.h" +#include "Statistics.h" + +#include "bandnorm.h" + +using namespace std; + +namespace Isis { + + static vector band; + static vector average; + static vector normalizer; + + // function prototypes + void getStats(Buffer &in); + void normalize(Buffer &in, Buffer &out); + void Tokenize(const QString &str, + vector & tokens, + const QString &delimiters = " "); + + void bandnorm(UserInterface &ui) { + Cube icube(ui.GetFileName("FROM"), "r"); + bandnorm(&icube, ui); + } + + + void bandnorm(Cube *icube, UserInterface &ui) { + // We will be processing by line + ProcessByLine p; + + // Now get the statistics for each band or the entire cube + QString avg = ui.GetString("AVERAGE"); + p.SetInputCube(icube); + p.StartProcess(getStats); + if(avg == "BAND") { + int b = 0; + Statistics stats; + for(int i = 0; i < (int)average.size(); i++) { + if(b == band[i]) { + stats.AddData(&average[i], (unsigned int)1); + } + else { + normalizer.push_back(stats.Average()); + b++; + stats.Reset(); + } + } + normalizer.push_back(stats.Average()); + } + else if(avg == "PENCIL") { + TextFile pencil; + pencil.Open(ui.GetFileName("SPECTRUM")); + std::cout << pencil.LineCount() << " " << icube->bandCount() << std::endl; + if(pencil.LineCount() - 1 < icube->bandCount()) { + QString msg = "The spectral pencil file [" + ui.GetAsString("SPECTRUM") + + "] does not contain enough data for all bands."; + throw IException(IException::User, msg, _FILEINFO_); + } + QString st; + int column = -1; + vector tokens; + pencil.GetLine(st); //Takes care of title line + Tokenize(st, tokens, ", \"-+"); + if(ui.GetAsString("METHOD") == "number") { + column = ui.GetInteger("NUMBER"); + } + else { + for(unsigned i = 0; i < tokens.size(); i++) { + if(tokens[i] == ui.GetString("NAME")) { + column = i; + break; + } + } + } + if(column < 0 || (unsigned)column > tokens.size()) { + QString msg = "The column specified in file [" + ui.GetFileName("SPECTRUM") + + "] was not found."; + throw IException(IException::User, msg, _FILEINFO_); + } + // Add the correct column of data to normalizer + for(int i = 0; i < icube->bandCount(); i++) { + tokens.clear(); + pencil.GetLine(st); + Tokenize(st, tokens, ", \""); + std::cout << "col: " << column << std::endl; + std::cout << Isis::IString(tokens[column]).ToDouble() << std::endl; + normalizer.push_back(Isis::IString(tokens[column]).ToDouble()); + } + } + else { // avg == "CUBE" + Statistics stats; + for(int i = 0; i < (int)average.size(); i++) { + stats.AddData(&average[i], (unsigned int)1); + } + for(int b = 0; b < icube->bandCount(); b++) { + normalizer.push_back(stats.Average()); + } + } + + // Setup the output file and apply the correction + p.SetOutputCube(ui.GetFileName("TO"), ui.GetOutputAttribute("TO"), icube->sampleCount(), icube->lineCount(), icube->bandCount()); + p.StartProcess(normalize); + + // Cleanup + p.EndProcess(); + normalizer.clear(); + band.clear(); + average.clear(); + } + + //********************************************************** + // Get statistics on a band or entire cube + //********************************************************** + void getStats(Buffer &in) { + Statistics stats; + stats.AddData(in.DoubleBuffer(), in.size()); + average.push_back(stats.Average()); + band.push_back(in.Band() - 1); + } + + // Apply coefficients + void normalize(Buffer &in, Buffer &out) { + int index = in.Band() - 1; + double coeff = normalizer[index]; + + // Now loop and apply the coefficents + for(int i = 0; i < in.size(); i++) { + if(IsSpecial(in[i])) { + out[i] = in[i]; + } + else { + out[i] = Null; + if(coeff != 0.0 && IsValidPixel(coeff)) { + out[i] = in[i] / coeff; + } + } + } + } + + // Tokenizer + void Tokenize(const QString &strQStr, + vector & tokens, + const QString &delimitersQStr) { + IString str = strQStr; + IString delimiters = delimitersQStr; + + //Skip delimiters at the beginning + string::size_type lastPos = str.find_first_not_of(delimiters, 0); + // Find first "non-delimiter". + string::size_type pos = str.find_first_of(delimiters, lastPos); + + while(string::npos != pos || string::npos != lastPos) { + // Found a token, add it to the vector + tokens.push_back(str.substr(lastPos, pos - lastPos).c_str()); + // Skip delimiters + lastPos = str.find_first_not_of(delimiters, pos); + // Find next "non-delimiter" + pos = str.find_first_of(delimiters, lastPos); + } + } +} \ No newline at end of file diff --git a/isis/src/base/apps/bandnorm/bandnorm.h b/isis/src/base/apps/bandnorm/bandnorm.h new file mode 100644 index 0000000000..a1800e4cbd --- /dev/null +++ b/isis/src/base/apps/bandnorm/bandnorm.h @@ -0,0 +1,12 @@ +#ifndef std2isis_h +#define std2isis_h + +#include "UserInterface.h" +#include "Cube.h" + +namespace Isis { + extern void bandnorm(Cube *icube, UserInterface &ui); + extern void bandnorm(UserInterface &ui); +} + +#endif \ No newline at end of file diff --git a/isis/src/base/apps/bandnorm/main.cpp b/isis/src/base/apps/bandnorm/main.cpp index be0c323bff..27706c5041 100644 --- a/isis/src/base/apps/bandnorm/main.cpp +++ b/isis/src/base/apps/bandnorm/main.cpp @@ -1,165 +1,11 @@ #include "Isis.h" -// system include files go first -#include -#include -#include -#include -#include +#include "Application.h" +#include "bandnorm.h" -// Isis specific include files go next -#include "ProcessByLine.h" -#include "SpecialPixel.h" -#include "IException.h" -#include "Pvl.h" -#include "TextFile.h" -#include "Statistics.h" - -using namespace std; using namespace Isis; -static vector band; -static vector average; -static vector normalizer; - -// function prototypes -void getStats(Buffer &in); -void normalize(Buffer &in, Buffer &out); -void Tokenize(const QString &str, - vector & tokens, - const QString &delimiters = " "); - void IsisMain() { - // We will be processing by line - ProcessByLine p; - - // Setup the input cube UserInterface &ui = Application::GetUserInterface(); - Cube *icube = p.SetInputCube("FROM"); - - // Now get the statistics for each band or the entire cube - QString avg = ui.GetString("AVERAGE"); - p.StartProcess(getStats); - if(avg == "BAND") { - int b = 0; - Statistics stats; - for(int i = 0; i < (int)average.size(); i++) { - if(b == band[i]) { - stats.AddData(&average[i], (unsigned int)1); - } - else { - normalizer.push_back(stats.Average()); - b++; - stats.Reset(); - } - } - normalizer.push_back(stats.Average()); - } - else if(avg == "PENCIL") { - TextFile pencil; - pencil.Open(ui.GetFileName("SPECTRUM")); - if(pencil.LineCount() - 1 < icube->bandCount()) { - QString msg = "The spectral pencil file [" + ui.GetAsString("SPECTRUM") + - "] does not contain enough data for all bands."; - throw IException(IException::User, msg, _FILEINFO_); - } - QString st; - int column = -1; - vector tokens; - pencil.GetLine(st); //Takes care of title line - Tokenize(st, tokens, ", \"-+"); - if(ui.GetAsString("METHOD") == "number") { - column = ui.GetInteger("NUMBER"); - } - else { - for(unsigned i = 0; i < tokens.size(); i++) { - if(tokens[i] == ui.GetString("NAME")) { - column = i; - break; - } - } - } - if(column < 0 || (unsigned)column > tokens.size()) { - QString msg = "The column specified in file [" + ui.GetFileName("SPECTRUM") - + "] was not found."; - throw IException(IException::User, msg, _FILEINFO_); - } - // Add the correct column of data to normalizer - for(int i = 0; i < icube->bandCount(); i++) { - tokens.clear(); - pencil.GetLine(st); - Tokenize(st, tokens, ", \""); - normalizer.push_back(Isis::IString(tokens[column]).ToDouble()); - } - } - else { // avg == "CUBE" - Statistics stats; - for(int i = 0; i < (int)average.size(); i++) { - stats.AddData(&average[i], (unsigned int)1); - } - for(int b = 0; b < icube->bandCount(); b++) { - normalizer.push_back(stats.Average()); - } - } - - // Setup the output file and apply the correction - p.SetOutputCube("TO"); - p.StartProcess(normalize); - - // Cleanup - p.EndProcess(); - normalizer.clear(); - band.clear(); - average.clear(); -} - -//********************************************************** -// Get statistics on a band or entire cube -//********************************************************** -void getStats(Buffer &in) { - Statistics stats; - stats.AddData(in.DoubleBuffer(), in.size()); - average.push_back(stats.Average()); - band.push_back(in.Band() - 1); -} - -// Apply coefficients -void normalize(Buffer &in, Buffer &out) { - int index = in.Band() - 1; - double coeff = normalizer[index]; - - // Now loop and apply the coefficents - for(int i = 0; i < in.size(); i++) { - if(IsSpecial(in[i])) { - out[i] = in[i]; - } - else { - out[i] = Null; - if(coeff != 0.0 && IsValidPixel(coeff)) { - out[i] = in[i] / coeff; - } - } - } -} - -// Tokenizer -void Tokenize(const QString &strQStr, - vector & tokens, - const QString &delimitersQStr) { - IString str = strQStr; - IString delimiters = delimitersQStr; - - //Skip delimiters at the beginning - string::size_type lastPos = str.find_first_not_of(delimiters, 0); - // Find first "non-delimiter". - string::size_type pos = str.find_first_of(delimiters, lastPos); - - while(string::npos != pos || string::npos != lastPos) { - // Found a token, add it to the vector - tokens.push_back(str.substr(lastPos, pos - lastPos).c_str()); - // Skip delimiters - lastPos = str.find_first_not_of(delimiters, pos); - // Find next "non-delimiter" - pos = str.find_first_of(delimiters, lastPos); - } -} + bandnorm(ui); +} \ No newline at end of file diff --git a/isis/src/base/apps/bandnorm/tsts/Makefile b/isis/src/base/apps/bandnorm/tsts/Makefile deleted file mode 100644 index 46d84c74c2..0000000000 --- a/isis/src/base/apps/bandnorm/tsts/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -BLANKS = "%-6s" -LENGTH = "%-40s" - -include $(ISISROOT)/make/isismake.tststree diff --git a/isis/src/base/apps/bandnorm/tsts/batchlist/Makefile b/isis/src/base/apps/bandnorm/tsts/batchlist/Makefile deleted file mode 100644 index 9b9fc4dcd5..0000000000 --- a/isis/src/base/apps/bandnorm/tsts/batchlist/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -APPNAME = bandnorm - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/\$$\1.cub \ - to=$(OUTPUT)/\$$\1.norm.cub average=band -batchlist=$(INPUT)/testInput.lis > /dev/null; diff --git a/isis/src/base/apps/bandnorm/tsts/default/Makefile b/isis/src/base/apps/bandnorm/tsts/default/Makefile deleted file mode 100644 index 37df99b1ab..0000000000 --- a/isis/src/base/apps/bandnorm/tsts/default/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -APPNAME = bandnorm - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/f348b26.cub \ - to=$(OUTPUT)/bandnormTruth.cub > /dev/null; diff --git a/isis/src/base/apps/bandnorm/tsts/pencil/Makefile b/isis/src/base/apps/bandnorm/tsts/pencil/Makefile deleted file mode 100644 index a2fe55328c..0000000000 --- a/isis/src/base/apps/bandnorm/tsts/pencil/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -APPNAME = bandnorm - -include $(ISISROOT)/make/isismake.tsts - -commands: - $(APPNAME) from=$(INPUT)/peaks.cub \ - to=$(OUTPUT)/bandnormPeaksTruth.cub \ - AVERAGE=pencil SPECTRUM=$(INPUT)/peakspencil.txt \ - > /dev/null; - $(APPNAME) from=$(INPUT)/m0402852.cub \ - to=$(OUTPUT)/bandnormM040Truth.cub \ - AVERAGE=pencil SPECTRUM=$(INPUT)/m040285pencil.txt \ - > /dev/null; - $(APPNAME) from=$(INPUT)/I00831002RDRcropped.cub \ - to=$(OUTPUT)/bandnormI008Truth.cub \ - AVERAGE=pencil SPECTRUM=$(INPUT)/I00831002RDRpencil.txt \ - > /dev/null; - $(APPNAME) from=$(INPUT)/I00831002RDRcropped.cub \ - to=$(OUTPUT)/bandnormI008NumTruth.cub \ - AVERAGE=pencil SPECTRUM=$(INPUT)/I00831002RDRpencil.txt \ - METHOD=bynumber NUMBER=6 \ - > /dev/null; diff --git a/isis/tests/FunctionalTestsBandNorm.cpp b/isis/tests/FunctionalTestsBandNorm.cpp new file mode 100644 index 0000000000..4a28646b66 --- /dev/null +++ b/isis/tests/FunctionalTestsBandNorm.cpp @@ -0,0 +1,242 @@ +#include "Fixtures.h" +#include "Pvl.h" +#include "PvlGroup.h" +#include "TestUtilities.h" +#include "LineManager.h" +#include "Histogram.h" + +#include "bandnorm.h" + +#include "gtest/gtest.h" + +using namespace Isis; + +static QString APP_XML = FileName("$ISISROOT/bin/xml/bandnorm.xml").expanded(); + +TEST_F(SmallCube, FunctionalTestsBandNormDefault) { + QString outCubeFileName = tempDir.path()+"/outTEMP.cub"; + + // force all bands to be normalized to 1's + LineManager line(*testCube); + double pixelValue = 10; + for(line.begin(); !line.end(); line++) { + for(int i = 0; i < line.size(); i++) { + line[i] = (double) pixelValue; + } + testCube->write(line); + } + QVector args = {"to="+outCubeFileName}; + + UserInterface options(APP_XML, args); + try { + bandnorm(testCube, options); + } + catch (IException &e) { + FAIL() << "Unable to process image: " << e.what() << std::endl; + } + + Cube oCube(outCubeFileName); + std::unique_ptr oCubeStats; + + for (int i = 1; i <= testCube->bandCount(); i++) { + oCubeStats.reset(oCube.histogram(i)); + ASSERT_DOUBLE_EQ(oCubeStats->Average(), 1); + ASSERT_DOUBLE_EQ(oCubeStats->Sum(), 100); + ASSERT_EQ(oCubeStats->ValidPixels(), 100); + ASSERT_DOUBLE_EQ(oCubeStats->StandardDeviation(), 0); + } +} + + +TEST_F(SmallCube, FunctionalTestsBandNormPencil) { + QString outCubeFileName = tempDir.path()+"/outTEMP.cub"; + QString pencilPath = "/tmp/pencil.txt"; + + QVector args = {"to="+outCubeFileName, "SPECTRUM="+pencilPath, "AVERAGE=PENCIL"}; + + QFile file(pencilPath); + file.open(QIODevice::WriteOnly); + QTextStream qout(&file); + qout << "\" Average\", \n"; + + for(int i = 1; i <=10; i++) { + QString text(QString::number(i) + "\n"); + qout << text; + } + file.close(); + + UserInterface options(APP_XML, args); + try { + bandnorm(testCube, options); + } + catch (IException &e) { + FAIL() << "Unable to process image: " << e.what() << std::endl; + } + + Cube oCube(outCubeFileName); + std::unique_ptr oCubeStats(oCube.histogram()); + ASSERT_DOUBLE_EQ(oCubeStats->Average(), 49.5); + ASSERT_DOUBLE_EQ(oCubeStats->Sum(), 4950); + ASSERT_EQ(oCubeStats->ValidPixels(), 100); + ASSERT_DOUBLE_EQ(oCubeStats->StandardDeviation(), 29.011491975882016); + + oCubeStats.reset(oCube.histogram(2)); + ASSERT_DOUBLE_EQ(oCubeStats->Average(), 74.75); + ASSERT_DOUBLE_EQ(oCubeStats->Sum(), 7475); + ASSERT_EQ(oCubeStats->ValidPixels(), 100); + ASSERT_DOUBLE_EQ(oCubeStats->StandardDeviation(), 14.505745987941008); + + oCubeStats.reset(oCube.histogram(3)); + ASSERT_DOUBLE_EQ(oCubeStats->Average(), 83.166666641235352); + ASSERT_DOUBLE_EQ(oCubeStats->Sum(), 8316.6666641235352); + ASSERT_EQ(oCubeStats->ValidPixels(), 100); + ASSERT_DOUBLE_EQ(oCubeStats->StandardDeviation(), 9.6704973399038625); + + oCubeStats.reset(oCube.histogram(4)); + EXPECT_NEAR(oCubeStats->Average(), 87.375, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 8737.5, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 7.25287, 0.0001); + + oCubeStats.reset(oCube.histogram(5)); + EXPECT_NEAR(oCubeStats->Average(), 89.9, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 8990, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 5.8023, 0.0001); + + oCubeStats.reset(oCube.histogram(6)); + EXPECT_NEAR(oCubeStats->Average(), 91.5833, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9158.3333358764648, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 4.83525, 0.0001); + + oCubeStats.reset(oCube.histogram(7)); + EXPECT_NEAR(oCubeStats->Average(), 92.7857, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9278.5714263916016, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 4.1445, 0.0001); + + oCubeStats.reset(oCube.histogram(8)); + EXPECT_NEAR(oCubeStats->Average(), 93.6875, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9368.75, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 3.62644, 0.0001); + + oCubeStats.reset(oCube.histogram(9)); + EXPECT_NEAR(oCubeStats->Average(), 94.3889, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9438.8888854980469, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 3.2235, 0.0001); + + oCubeStats.reset(oCube.histogram(10)); + EXPECT_NEAR(oCubeStats->Average(), 94.95, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9495, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 2.90115, 0.0001); +} + + +TEST_F(SmallCube, FunctionalTestsBandNormByNumber) { + QString outCubeFileName = tempDir.path()+"/outTEMP.cub"; + QString pencilPath = "/tmp/pencil.txt"; + + QVector args = {"to="+outCubeFileName, "SPECTRUM="+pencilPath, "AVERAGE=PENCIL", + "method=number", "number=1"}; + + QFile file(pencilPath); + file.open(QIODevice::WriteOnly); + QTextStream qout(&file); + qout << "\" Average\", \" eh\", \n"; + + for(int i = 1; i <=10; i++) { + int n = 1; + if (i == 7) n = 7; + + QString text = QString::number(i) + "," + QString::number(n) + "\n"; + qout << text; + } + + file.close(); + + UserInterface options(APP_XML, args); + try { + bandnorm(testCube, options); + } + catch (IException &e) { + FAIL() << "Unable to process image: " << e.what() << std::endl; + } + + Cube oCube(outCubeFileName); + std::unique_ptr oCubeStats(oCube.histogram(7)); + EXPECT_NEAR(oCubeStats->Average(), 92.7857, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9278.5714263916016, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 4.1445, 0.0001); + + // double check to see that other bands were not changed + oCubeStats.reset(oCube.histogram(2)); + ASSERT_DOUBLE_EQ(oCubeStats->Average(), 149.5); + ASSERT_DOUBLE_EQ(oCubeStats->Sum(), 14950); + ASSERT_EQ(oCubeStats->ValidPixels(), 100); + ASSERT_DOUBLE_EQ(oCubeStats->StandardDeviation(), 29.011491975882016); +} + + +TEST_F(SmallCube, FunctionalTestsBandNormByBandAvg) { + QString outCubeFileName = tempDir.path()+"/outTEMP.cub"; + QString pencilPath = "/tmp/pencil.txt"; + + QVector args = {"to="+outCubeFileName, "AVERAGE=Band"}; + + UserInterface options(APP_XML, args); + try { + bandnorm(testCube, options); + } + catch (IException &e) { + FAIL() << "Unable to process image: " << e.what() << std::endl; + } + + Cube oCube(outCubeFileName); + std::unique_ptr oCubeStats(oCube.histogram(1)); + EXPECT_NEAR(oCubeStats->Average(), 1, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 100.0, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 0.586090, 0.0001); + + // double check to see that other bands were not changed + oCubeStats.reset(oCube.histogram(2)); + EXPECT_NEAR(oCubeStats->Average(), 0.96763754, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 96.76375418, 0.0001); + EXPECT_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 0.1877766488, 0.0001); +} + + +TEST_F(SmallCube, FunctionalTestsBandNormByCubeAvg) { + QString outCubeFileName = tempDir.path()+"/outTEMP.cub"; + QString pencilPath = "/tmp/pencil.txt"; + + QVector args = {"to="+outCubeFileName, "AVERAGE=Cube"}; + + UserInterface options(APP_XML, args); + try { + bandnorm(testCube, options); + } + catch (IException &e) { + FAIL() << "Unable to process image: " << e.what() << std::endl; + } + + Cube oCube(outCubeFileName); + std::unique_ptr oCubeStats(oCube.histogram(1)); + EXPECT_NEAR(oCubeStats->Average(), 0.099099099056329576, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 9.9099099056329578, 0.0001); + ASSERT_DOUBLE_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 0.05808106515551998, 0.0001); + + // double check to see that other bands were not changed + oCubeStats.reset(oCube.histogram(2)); + EXPECT_NEAR(oCubeStats->Average(), 0.29929929912090303, 0.0001); + EXPECT_NEAR(oCubeStats->Sum(), 29.929929912090302, 0.0001); + EXPECT_EQ(oCubeStats->ValidPixels(), 100); + EXPECT_NEAR(oCubeStats->StandardDeviation(), 0.058081065874528971, 0.0001); +} \ No newline at end of file