Commit 4b474f53 authored by Jesse Mapel's avatar Jesse Mapel
Browse files

Refactored stats to a functions

parent 6bf151a4
Loading
Loading
Loading
Loading
+175 −0
Original line number Diff line number Diff line
#include "StatsFunc.h"

#include <string>
#include <iostream>

#include "Application.h"
#include "CubeAttribute.h"
#include "Cube.h"
#include "FileName.h"
#include "Histogram.h"
#include "Pvl.h"
#include "UserInterface.h"

using namespace std;
using namespace Isis;

namespace Isis {

  /**
   * Compute the stats for an ISIS cube. This is the programmatic interface to
   * the ISIS3 stats application.
   *
   * @param ui The User Interface to parse the parameters from
   */
  void stats(UserInterface &ui) {

    Cube *inputCube = new Cube();
    CubeAttributeInput inAtt(ui.GetAsString("FROM"));
    inputCube->setVirtualBands(inAtt.bands());
    inputCube->open(ui.GetFileName("FROM"));

    double validMin = Isis::ValidMinimum;
    double validMax = Isis::ValidMaximum;

    if ( ui.WasEntered("VALIDMIN") ) {
      validMin = ui.GetDouble("VALIDMIN");
    }

    if ( ui.WasEntered("VALIDMAX") ) {
      validMax = ui.GetDouble("VALIDMAX");
    }

    Pvl statsPvl = stats(inputCube, validMin, validMax);

    for (int resultIndex = 0; resultIndex < statsPvl.groups(); resultIndex++) {
      if (statsPvl.group(resultIndex).name() == "Results") {
        Application::Log(statsPvl.group(resultIndex));
      }
    }

    delete inputCube;
    inputCube = nullptr;

    if ( ui.WasEntered("TO") ) {
      QString outFile = FileName(ui.GetFileName("TO")).expanded();
      bool append = ui.GetBoolean("APPEND");
      //write the results in the requested format.
      if ( ui.GetString("FORMAT") == "PVL" ) {
        if (append) {
          statsPvl.append(outFile);
        }
        else {
          statsPvl.write(outFile);
        }
      }
      else {
        bool exists = FileName(outFile).fileExists();
        bool writeHeader = false;
        ofstream *os = new ofstream;
        if (append) {
          os->open(outFile.toLatin1().data(), ios::app);
          if (!exists) {
            writeHeader = true;
          }
        }
        else {
          os->open(outFile.toLatin1().data(), ios::out);
          writeHeader = true;
        }
        writeStatsStream(statsPvl, writeHeader, os);
        delete os;
        os = nullptr;
      }
    }
  }


  /**
   * Compute statistics about a Cube and store them in a PVL object.
   *
   * @param cube The cube to compute the statistics of
   * @param validMin The minimum pixel value to include in the statistics
   * @param validMax The maximum pixel value to include in the statistics
   *
   * @return @b Pvl The statistics for the cube in a Pvl object
   *
   * @see Cube::histogram
   */
  Pvl stats(Cube *cube, double validMin, double validMax) {

    // Set a global Pvl for storing results
    Pvl statsPvl;

    // Get the number of bands to process
    int bandCount = cube->bandCount();

    for (int i = 1; i <= bandCount; i++) {
      Histogram *stats = cube->histogram(i, validMin, validMax);

      // Construct a label with the results
      PvlGroup results("Results");
      results += PvlKeyword("From", cube->fileName());
      results += PvlKeyword("Band", toString(cube->physicalBand(i)));
      if ( stats->ValidPixels() != 0 ) {
        results += PvlKeyword("Average", toString(stats->Average()));
        results += PvlKeyword("StandardDeviation", toString(stats->StandardDeviation()));
        results += PvlKeyword("Variance", toString(stats->Variance()));
        // These statistics only worked on a histogram
        results += PvlKeyword("Median", toString(stats->Median()));
        results += PvlKeyword("Mode", toString(stats->Mode()));
        results += PvlKeyword("Skew", toString(stats->Skew()));
        results += PvlKeyword("Minimum", toString(stats->Minimum()));
        results += PvlKeyword("Maximum", toString(stats->Maximum()));
        results += PvlKeyword("Sum", toString(stats->Sum()));
      }
      results += PvlKeyword("TotalPixels", toString(stats->TotalPixels()));
      results += PvlKeyword("ValidPixels", toString(stats->ValidPixels()));
      results += PvlKeyword("OverValidMaximumPixels", toString(stats->OverRangePixels()));
      results += PvlKeyword("UnderValidMinimumPixels", toString(stats->UnderRangePixels()));
      results += PvlKeyword("NullPixels", toString(stats->NullPixels()));
      results += PvlKeyword("LisPixels", toString(stats->LisPixels()));
      results += PvlKeyword("LrsPixels", toString(stats->LrsPixels()));
      results += PvlKeyword("HisPixels", toString(stats->HisPixels()));
      results += PvlKeyword("HrsPixels", toString(stats->HrsPixels()));

      statsPvl.addGroup(results);

      delete stats;
      stats = nullptr;
    }

    return statsPvl;
  }


  /**
   * Write a statistics Pvl to an output stream in a CSV format.
   *
   * @param statsPvl The Pvl to write out
   * @param writeHeader If a header line should be written
   * @param stram The stream to write to
   */
  void writeStatsStream(const Pvl &statsPvl, bool writeHeader, ostream *stream) {
    if (writeHeader) {
      for (int i = 0; i < statsPvl.group(0).keywords(); i++) {
        *stream << statsPvl.group(0)[i].name();
        if ( i < statsPvl.group(0).keywords() - 1 ) {
          *stream << ",";
        }
      }
      *stream << endl;
    }

    for (int i = 0; i < statsPvl.groups(); i++) {
      for (int j = 0; j < statsPvl.group(i).keywords(); j++) {
        *stream << (QString) statsPvl.group(i)[j];
        if ( j < statsPvl.group(i).keywords() - 1 ) {
          *stream << ",";
        }
      }
      *stream << endl;
    }
  }

}
+19 −0
Original line number Diff line number Diff line
#include <iostream>
#include <vector>

#include <QString>

#include "Cube.h"
#include "UserInterface.h"

namespace Isis {
  extern void stats(UserInterface &ui);
  extern Pvl stats(
        Cube *cube,
        double validMin,
        double validMax);
  extern void writeStatsStream(
        const Pvl &statsPvl,
        bool writeHeader,
        std::ostream *stream);
}
+3 −119
Original line number Diff line number Diff line
#include "Isis.h"

#include <string>
#include <iostream>
#include "UserInterface.h"
#include "StatsFunc.h"

#include "Cube.h"
#include "Process.h"
#include "Histogram.h"
#include "Pvl.h"

using namespace std;
using namespace Isis;

void IsisMain() {

  UserInterface &ui = Application::GetUserInterface();
  Process process;

  // Get the histogram
  Cube *inputCube = process.SetInputCube("FROM");

  double validMin = Isis::ValidMinimum;
  double validMax = Isis::ValidMaximum;

  if ( ui.WasEntered("VALIDMIN") ) {
    validMin = ui.GetDouble("VALIDMIN");
  }

  if ( ui.WasEntered("VALIDMAX") ) {
    validMax = ui.GetDouble("VALIDMAX");
  }
  
  // Set a global Pvl for storing results
  Pvl mainPvl;
  
  // Get the number of bands to process
  int bandCount = inputCube->bandCount();
  
  for (int i = 1; i <= bandCount; i++) {
    Histogram *stats = inputCube->histogram(i, validMin, validMax);

    // Construct a label with the results
    PvlGroup results("Results");  
    results += PvlKeyword("From", inputCube->fileName());
    results += PvlKeyword("Band", toString(inputCube->physicalBand(i)));
    if ( stats->ValidPixels() != 0 ) {
      results += PvlKeyword("Average", toString(stats->Average()));
      results += PvlKeyword("StandardDeviation", toString(stats->StandardDeviation()));
      results += PvlKeyword("Variance", toString(stats->Variance()));
      // These statistics only worked on a histogram
      results += PvlKeyword("Median", toString(stats->Median()));
      results += PvlKeyword("Mode", toString(stats->Mode()));
      results += PvlKeyword("Skew", toString(stats->Skew()));
      results += PvlKeyword("Minimum", toString(stats->Minimum()));
      results += PvlKeyword("Maximum", toString(stats->Maximum()));
      results += PvlKeyword("Sum", toString(stats->Sum()));
    }
    results += PvlKeyword("TotalPixels", toString(stats->TotalPixels()));
    results += PvlKeyword("ValidPixels", toString(stats->ValidPixels()));
    results += PvlKeyword("OverValidMaximumPixels", toString(stats->OverRangePixels()));
    results += PvlKeyword("UnderValidMinimumPixels", toString(stats->UnderRangePixels()));
    results += PvlKeyword("NullPixels", toString(stats->NullPixels()));
    results += PvlKeyword("LisPixels", toString(stats->LisPixels()));
    results += PvlKeyword("LrsPixels", toString(stats->LrsPixels()));
    results += PvlKeyword("HisPixels", toString(stats->HisPixels()));
    results += PvlKeyword("HrsPixels", toString(stats->HrsPixels()));
    
    mainPvl.addGroup(results);
    
    delete stats;
    stats = NULL;

    // Write the results to the log
    Application::Log(results);
  }
  
  // Write the results to the output file if the user specified one
  if ( ui.WasEntered("TO") ) {
    QString outFile = FileName(ui.GetFileName("TO")).expanded();
    bool exists = FileName(outFile).fileExists();
    bool append = ui.GetBoolean("APPEND");
    ofstream os;
    bool writeHeader = false;
    //write the results in the requested format.
    if ( ui.GetString("FORMAT") == "PVL" ) {
      if (append) {
        mainPvl.append(outFile);
      }
      else {
        mainPvl.write(outFile);
      }
    }
    else {
      //if the format was not PVL, write out a flat file.
      if (append) {
        os.open(outFile.toLatin1().data(), ios::app);
        if (!exists) {
          writeHeader = true;
        }
      }
      else {
        os.open(outFile.toLatin1().data(), ios::out);
        writeHeader = true;
      }

      if (writeHeader) {
        for (int i = 0; i < mainPvl.group(0).keywords(); i++) {
          os << mainPvl.group(0)[i].name();
          if ( i < mainPvl.group(0).keywords() - 1 ) {
            os << ",";
          }
        }
        os << endl;
      }
      
      for (int i = 0; i < mainPvl.groups(); i++) {
        for (int j = 0; j < mainPvl.group(i).keywords(); j++) {
          os << (QString) mainPvl.group(i)[j];
          if ( j < mainPvl.group(i).keywords() - 1 ) {
            os << ",";
          }
        }
        os << endl;
      }
    }
  }
  stats(ui);
}
+9 −8
Original line number Diff line number Diff line
@@ -164,6 +164,7 @@ namespace Isis {
   *                           make sure we get absolute path.  Fixes #5276.
   *   @history 2018-01-18 Summer Stapleton - Updated error message in ::create() to address when
   *                           an IsisPreference file cannot be found. Fixes #5145.
   *   @history 2018-11-16 Jesse Mapel - Made several methods virtual for mocking.
   */
  class Cube {
    public:
@@ -265,16 +266,16 @@ namespace Isis {
      void relocateDnData(FileName dnDataFile);
//       static void relocateDnData(FileName externalLabelFile, FileName dnDataFile);

      int bandCount() const;
      virtual int bandCount() const;
      double base() const;
      ByteOrder byteOrder() const;
      Camera *camera();
      FileName externalCubeFileName() const;
      QString fileName() const;
      virtual QString fileName() const;
      Format format() const;
      Histogram *histogram(const int &band = 1,
      virtual Histogram *histogram(const int &band = 1,
                                   QString msg = "Gathering histogram");
      Histogram *histogram(const int &band, const double &validMin,
      virtual Histogram *histogram(const int &band, const double &validMin,
                                   const double &validMax,
                                   QString msg = "Gathering histogram");
      Pvl *label() const;
@@ -282,7 +283,7 @@ namespace Isis {
      int lineCount() const;
      double multiplier() const;
      PixelType pixelType() const;
      int physicalBand(const int &virtualBand) const;
      virtual int physicalBand(const int &virtualBand) const;
      Projection *projection();
      int sampleCount() const;
      Statistics *statistics(const int &band = 1,
+209 −0
Original line number Diff line number Diff line
#include "StatsFunc.h"

#include <iostream>

#include "gmock/gmock.h"

#include "Cube.h"
#include "FileName.h"
#include "Histogram.h"
#include "Pvl.h"
#include "SpecialPixel.h"

using namespace Isis;

class MockCube : public Cube {
  public:
    MOCK_CONST_METHOD0(bandCount, int());
    MOCK_CONST_METHOD0(fileName, QString());
    MOCK_CONST_METHOD1(physicalBand, int(const int &virtualBand));
    MOCK_METHOD4(histogram, Histogram*(
          const int &band, const double &validMin,
          const double &validMax,
          QString msg));
};

class StatsFunc_FlatFileTest : public ::testing::Test {
  protected:
    Pvl testPvl;

    void SetUp() override {
      PvlGroup firstGroup("FirstGroup");
      firstGroup += PvlKeyword("NumberKey", "0.0");
      firstGroup += PvlKeyword("StringKey", "Hello");
      testPvl += firstGroup;

      PvlGroup secondGroup("SecondGroup");
      PvlKeyword dupKey("DuplicateKey", "stats here");
      secondGroup += dupKey;
      secondGroup += dupKey;
      testPvl += secondGroup;
    }
};

class StatsFunc_MockHist : public ::testing::Test {
  protected:
    MockCube *mockCube;

    void SetUp() override {
      mockCube = nullptr;

      Histogram *testBand1Stats = new Histogram(-10, 10, 21);
      for (int val = -10; val <=10; val++) {
        testBand1Stats->AddData(val);
      }
      testBand1Stats->AddData(0.0);

      Histogram *testBand2Stats = new Histogram(-10, 10, 21);
      testBand2Stats->AddData(Null);
      testBand2Stats->AddData(Lrs);
      testBand2Stats->AddData(Lis);
      testBand2Stats->AddData(His);
      testBand2Stats->AddData(Hrs);

      mockCube = new MockCube();
      EXPECT_CALL(*mockCube, bandCount())
            .Times(1)
            .WillOnce(::testing::Return(2));
      EXPECT_CALL(*mockCube, histogram(1, ::testing::_, ::testing::_, ::testing::_))
            .Times(1)
            .WillOnce(::testing::Return(testBand1Stats));
      EXPECT_CALL(*mockCube, histogram(2, ::testing::_, ::testing::_, ::testing::_))
            .Times(1)
            .WillOnce(::testing::Return(testBand2Stats));
      EXPECT_CALL(*mockCube, fileName())
            .Times(2)
            .WillRepeatedly(::testing::Return("TestCube.cub"));
      EXPECT_CALL(*mockCube, physicalBand(1))
            .Times(1)
            .WillOnce(::testing::Return(1));
      EXPECT_CALL(*mockCube, physicalBand(2))
            .Times(1)
            .WillOnce(::testing::Return(2));
    }

    void TearDown() override {
      if (mockCube) {
        delete mockCube;
        mockCube = nullptr;
      }
      // The histograms will be cleaned up by the stats function
    }
};


TEST_F(StatsFunc_MockHist, TestStats) {
  Pvl statsPvl = stats(
        mockCube,
        Isis::ValidMinimum,
        Isis::ValidMaximum);

  ASSERT_EQ(statsPvl.groups(), 2);

  PvlGroup band1Stats = statsPvl.group(0);
  EXPECT_EQ("TestCube.cub", (QString) (band1Stats.findKeyword("From")));
  EXPECT_EQ(1, (int) (band1Stats.findKeyword("Band")));
  EXPECT_EQ(22, (int) (band1Stats.findKeyword("ValidPixels")));
  EXPECT_EQ(22, (int) (band1Stats.findKeyword("TotalPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("OverValidMaximumPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("UnderValidMinimumPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("NullPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("LisPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("LrsPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("HisPixels")));
  EXPECT_EQ(0, (int) (band1Stats.findKeyword("HrsPixels")));
  EXPECT_EQ(0.0, (double) (band1Stats.findKeyword("Average")));
  EXPECT_NEAR(6.0553, (double) (band1Stats.findKeyword("StandardDeviation")), 0.0001);
  EXPECT_NEAR(36.6667, (double) (band1Stats.findKeyword("Variance")), 0.0001);
  EXPECT_EQ(0.0, (double) (band1Stats.findKeyword("Median")));
  EXPECT_EQ(0.0, (double) (band1Stats.findKeyword("Mode")));
  EXPECT_EQ(0.0, (double) (band1Stats.findKeyword("Skew")));
  EXPECT_EQ(-10, (double) (band1Stats.findKeyword("Minimum")));
  EXPECT_EQ(10.0, (double) (band1Stats.findKeyword("Maximum")));
  EXPECT_EQ(0.0, (double) (band1Stats.findKeyword("Sum")));

  PvlGroup band2Stats = statsPvl.group(1);
  EXPECT_EQ("TestCube.cub", (QString) (band2Stats.findKeyword("From")));
  EXPECT_EQ(2, (int) (band2Stats.findKeyword("Band")));
  EXPECT_EQ(0, (int) (band2Stats.findKeyword("ValidPixels")));
  EXPECT_EQ(5, (int) (band2Stats.findKeyword("TotalPixels")));
  EXPECT_EQ(0, (int) (band2Stats.findKeyword("OverValidMaximumPixels")));
  EXPECT_EQ(0, (int) (band2Stats.findKeyword("UnderValidMinimumPixels")));
  EXPECT_EQ(1, (int) (band2Stats.findKeyword("NullPixels")));
  EXPECT_EQ(1, (int) (band2Stats.findKeyword("LisPixels")));
  EXPECT_EQ(1, (int) (band2Stats.findKeyword("LrsPixels")));
  EXPECT_EQ(1, (int) (band2Stats.findKeyword("HisPixels")));
  EXPECT_EQ(1, (int) (band2Stats.findKeyword("HrsPixels")));
}

TEST(StatsFunc, ValidMinimum) {
  Histogram *testStats = new Histogram(-1000,1000);

  MockCube *mockCube = new MockCube();
  EXPECT_CALL(*mockCube, bandCount())
        .Times(1)
        .WillOnce(::testing::Return(1));
  EXPECT_CALL(*mockCube, histogram(1, 0.0, ::testing::_, ::testing::_))
        .Times(1)
        .WillOnce(::testing::Return(testStats));
  EXPECT_CALL(*mockCube, fileName())
        .Times(1)
        .WillRepeatedly(::testing::Return("TestCube.cub"));
  EXPECT_CALL(*mockCube, physicalBand(1))
        .Times(1)
        .WillOnce(::testing::Return(1));

  Pvl statsPvl = stats(
        dynamic_cast<Cube*>(mockCube),
        0.0,
        Isis::ValidMaximum);

  delete mockCube;
  mockCube = nullptr;
  // The histogram will be cleaned up in the stats function
}

TEST(StatsFunc, ValidMaximum) {
  Histogram *testStats = new Histogram(-1000,1000);

  MockCube *mockCube = new MockCube();
  EXPECT_CALL(*mockCube, bandCount())
        .Times(1)
        .WillOnce(::testing::Return(1));
  EXPECT_CALL(*mockCube, histogram(1, ::testing::_, 0.0, ::testing::_))
        .Times(1)
        .WillOnce(::testing::Return(testStats));
  EXPECT_CALL(*mockCube, fileName())
        .Times(1)
        .WillRepeatedly(::testing::Return("TestCube.cub"));
  EXPECT_CALL(*mockCube, physicalBand(1))
        .Times(1)
        .WillOnce(::testing::Return(1));

  Pvl statsPvl = stats(
        dynamic_cast<Cube*>(mockCube),
        Isis::ValidMinimum,
        0.0);

  delete mockCube;
  mockCube = nullptr;
  // The histogram will be cleaned up in the stats function
}

TEST_F(StatsFunc_FlatFileTest, FlatFile) {
  std::ostringstream *testStream = new std::ostringstream();
  writeStatsStream(testPvl, false, testStream);
  EXPECT_EQ(testStream->str(), "0.0,Hello\nstats here,stats here\n");

  delete testStream;
  testStream = nullptr;
}

TEST_F(StatsFunc_FlatFileTest, FlatFileHeader) {
  std::ostringstream *testStream = new std::ostringstream();
  writeStatsStream(testPvl, true, testStream);
  EXPECT_EQ(testStream->str(), "NumberKey,StringKey\n0.0,Hello\nstats here,stats here\n");

  delete testStream;
  testStream = nullptr;
}