Unverified Commit 26b5fd8c authored by AustinSanders's avatar AustinSanders Committed by GitHub
Browse files

Initial findfeatures app conversion + gtests (#4163)

* Initial findfeatures conversion + gtests

* Delete makefiles

* Addressed PR feedback

Removed unnecessary cube.h from findfeatures.h
Removed additional cube reopens from threeimagenetwork fixture.
Added a comment clarifying empty lat/lon values in match test.

* Removed template comment

* Removed unused AddFeatures function

* Edited test for lat/lon in geomsource

* Edited maxpoints test to compare multiple runs of findfeatures
parent 87c8432b
Loading
Loading
Loading
Loading
+387 −0
Original line number Diff line number Diff line
#include <cmath>
#include <ctime>
/*#define USE_GUI_QAPP 1*/

#include <QApplication>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QFile>
#include <QTextStream>

// OpenCV stuff
#include "opencv2/core.hpp"

// boost library
#include <boost/foreach.hpp>

#define HAVE_ISNAN

#include "Camera.h"
#include "ControlMeasure.h"
#include "ControlMeasureLogData.h"
#include "ControlNet.h"
#include "ControlPoint.h"
#include "Cube.h"
#include "Distance.h"
#include "Environment.h"

#include "FastGeom.h"
#include "FeatureAlgorithmFactory.h"
#include "FileList.h"
#include "GenericTransform.h"
#include "ID.h"
#include "ImageSource.h"
#include "ImageTransform.h"
#include "ScalingTransform.h"

#include "IException.h"
#include "IString.h"
#include "iTime.h"
#include "Latitude.h"
#include "Longitude.h"
#include "MatcherSolution.h"
#include "MatchImage.h"
#include "MatchMaker.h"

#include "Process.h"
#include "Progress.h"
#include "PvlFlatMap.h"
#include "PvlGroup.h"
#include "QDebugLogger.h"
#include "RobustMatcher.h"
#include "SerialNumber.h"
#include "ScharrTransform.h"
#include "SobelTransform.h"
#include "Statistics.h"
#include "SurfacePoint.h"
#include "TextFile.h"
#include "UserInterface.h"
#include "Pvl.h"

using namespace std;
namespace Isis{

  static void writeInfo(const QString &toname, Pvl &data, UserInterface &ui, Pvl *log) {
      if ( !toname.isEmpty() ) {
        FileName toinfo(toname);
        QString fname = toinfo.expanded();
        data.write(fname);
      }
      else {
        if ( !ui.IsInteractive()  ) {
          std::cout << data << "\n";
        }
        else {
          if (log){
            PvlGroup results = data.findGroup("Results");
            log->addGroup(results);
          }
        }
      }
      return;
  }

  void findfeatures(UserInterface &ui, Pvl *log) {

    //  Program constants
    const QString findfeatures_program = "findfeatures";
    const QString findfeatures_version = "1.0";
    const QString findfeatures_revision = "$Revision$";

    // Get time for findfeatures_runtime
    time_t startTime = time(NULL);
    struct tm *tmbuf = localtime(&startTime);
    char timestr[80];
    strftime(timestr, 80, "%Y-%m-%dT%H:%M:%S", tmbuf);
    const QString findfeatures_runtime = (QString) timestr;

    QString toinfo;
    if ( ui.WasEntered("TOINFO")) {
      toinfo = ui.GetAsString("TOINFO");
    }

    // Set up program debugging and logging
    QDebugStream logger( QDebugLogger::null() );
    bool p_debug( ui.GetBoolean("DEBUG") );
    if ( p_debug ) {
      // User specified a file by name
      if ( ui.WasEntered("DEBUGLOG") ) {
        logger = QDebugLogger::create( ui.GetAsString("DEBUGLOG") );
      }
      else {
        // User wants debugging but entered no file so give'em std::cout.
        logger = QDebugLogger::toStdOut();
      }
    }

    // Write out program logger information
    logger->dbugout() << "\n\n---------------------------------------------------\n";
    logger->dbugout() << "Program:        " << findfeatures_program << "\n";
    logger->dbugout() << "Version         " << findfeatures_version << "\n";
    logger->dbugout() << "Revision:       " << findfeatures_revision << "\n";
    logger->dbugout() << "RunTime:        " << findfeatures_runtime << "\n";
    logger->dbugout() << "OpenCV_Version: " << CV_VERSION << "\n";
    logger->flush();

    // Set up for info requests
    FeatureAlgorithmFactory *factory = FeatureAlgorithmFactory::getInstance();
    if ( ui.GetBoolean("LISTALL") ) {
      Pvl info;
      QStringList algorithms = factory->getListAll();
      // cout << algorithms.join("\n") << "\n";
      info.addObject( factory->info(algorithms) );
      writeInfo(toinfo, info, ui, log);
      return;
    }


    // Get parameters from user
    PvlFlatMap parameters;
    // Check for parameters file provided by user
    if ( ui.WasEntered("PARAMETERS") ) {
      QString pfilename = ui.GetAsString("PARAMETERS");
      Pvl pfile(pfilename);
      parameters.merge( PvlFlatMap(pfile) );
      parameters.add("ParameterFile", pfilename);
    }

    // Get individual parameters if provided
    QStringList parmlist;
    parmlist << "Ratio" << "EpiTolerance" << "EpiConfidence" << "HmgTolerance"
             << "MaxPoints" << "FastGeom" << "FastGeomPoints" << "GeomType"
             << "GeomSource" << "Filter";
    BOOST_FOREACH (QString p, parmlist ) {
      parameters.add(p, ui.GetAsString(p));
    }

    // Got all parameters.  Add them now and they don't need to be considered
    // from here on.  Parameters specified in input algorithm specs take
    // precedence (in MatchMaker)
    factory->setGlobalParameters(parameters);

    // Retrieve the ALGORITHM specification (if requested)
    QString aspec;
    if ( ui.WasEntered("ALGORITHM") ) {
      aspec = ui.GetString("ALGORITHM");
    }

    // Now check for file containing algorithms
    if ( ui.WasEntered("ALGOSPECFILE") ) {
      QString specfile = ui.GetAsString("ALGOSPECFILE");
      TextFile sFile(specfile);
      sFile.OpenChk(true);
      QString algorithm;
      QStringList algos;
      while ( sFile.GetLine(algorithm, true) ) {
        algos.append(algorithm);
      }

      if ( !aspec.isEmpty() ) { aspec.append("|");  }
      aspec.append( algos.join("|") );
    }


    // Create a list of algorithm specifications from user specs and log it
    // if requested
    RobustMatcherList algorithms = factory->create(aspec);
    if ( ui.GetBoolean("LISTSPEC") ) {
      Pvl info;
      info.addObject(factory->info(algorithms));
      writeInfo(toinfo, info, ui, log);

      // If no input files are provided exit here
      if ( ! ( ui.WasEntered("FROM") && ui.WasEntered("FROMLIST") ) ) {
        return;
      }
    }

    // First see what we can do about threads if your user is resource conscience
    int nCPUs = cv::getNumberOfCPUs();
    int nthreads = cv::getNumThreads();
    logger->dbugout() << "\nSystem Environment...\n";
    logger->dbugout() << "Number available CPUs:     " << nCPUs << "\n";
    logger->dbugout() << "Number default threads:    " << nthreads << "\n";

    // See if user wants to restrict the number of threads used
    int uthreads = nthreads;
    if ( ui.WasEntered("MAXTHREADS") ) {
      uthreads = ui.GetInteger("MAXTHREADS");
      if (uthreads < nthreads) cv::setNumThreads(uthreads);
      logger->dbugout() << "User restricted threads:   " << uthreads << "\n";
    }
    int total_threads = cv::getNumThreads();
    logger->dbugout() << "Total threads:             " << total_threads << "\n";
    logger->flush();


    //-------------Matching Business--------------------------
    // Make the matcher class
    MatchMaker matcher(ui.GetString("NETWORKID"));
    matcher.setDebugLogger(logger, p_debug );

    // Acquire query image
    matcher.setQueryImage(MatchImage(ImageSource(ui.GetAsString("MATCH"))));

    // Get the trainer images
    if ( ui.WasEntered("FROM") ) {
      matcher.addTrainImage(MatchImage(ImageSource(ui.GetAsString("FROM"))));
    }

    // If there is a list provided, get that too
    if ( ui.WasEntered("FROMLIST") ) {
      FileList trainers(ui.GetFileName("FROMLIST"));
      BOOST_FOREACH ( FileName tfile, trainers ) {
        matcher.addTrainImage(MatchImage(ImageSource(tfile.original())));
      }
    }

    // Got to have both file names provided at this point
    if ( matcher.size() <= 0 ) {
      throw IException(IException::User,
                       "Must provide both a FROM/FROMLIST and MATCH cube or image filename",
                       _FILEINFO_);
    }

    // Define which geometry source we should use.  None is the default
    QString geomsource = ui.GetString("GEOMSOURCE").toLower();
    if ( "match" == geomsource ) { matcher.setGeometrySourceFlag(MatchMaker::Query); }
    if ( "from"  == geomsource ) { matcher.setGeometrySourceFlag(MatchMaker::Train); }

    // Check for FASTGEOM option
    if ( ui.GetBoolean("FASTGEOM") ) {
      FastGeom geom( factory->globalParameters() );
      matcher.foreachPair( geom );
    }

    // Check for Sobel/Scharr filtering options for both Train and Images
    QString filter = factory->globalParameters().get("FILTER", "").toLower();
    // Apply the Sobel filter to all image
    if ( "sobel" == filter ) {
      matcher.query().addTransform(new SobelTransform("SobelTransform"));
      for (int i = 0 ; i < matcher.size() ; i++) {
        matcher.train(i).addTransform(new SobelTransform("SobelTransform"));
      }
    }

    // Add the Scharr filter to all images
    if ( "scharr" == filter ) {
      matcher.query().addTransform(new ScharrTransform("ScharrTransform"));
      for (int i = 0 ; i < matcher.size() ; i++) {
        matcher.train(i).addTransform(new ScharrTransform("ScharrTransform"));
      }
    }


    //  Apply all matcher/transform permutations
    logger->dbugout() << "\nTotal Algorithms to Run:     " << algorithms.size() << "\n";
    MatcherSolutionList matches = matcher.match(algorithms);
    const MatcherSolution *best = MatcherSolution::best(matches);
    logger->dbugout().flush();

    // If all failed, we're done
    if ( !best ) {
      logger->dbugout() << "Bummer! No matches were found!\n";
      QString mess = "NO MATCHES WERE FOUND!!!";
      throw IException(IException::User, mess, _FILEINFO_);
    }

    // Not valid results
    if ( best->size() <= 0 ) {
      logger->dbugout() << "Shucks! Insufficient matches were found ("
                        << best->size() << ")\n";
      QString mess = "Shucks! Insufficient matches were found (" +
                     QString::number(best->size()) + ")";
      throw IException(IException::User, mess, _FILEINFO_);
    }

    // Got some matches so lets process them
    Statistics quality = best->qualityStatistics();
    PvlGroup bestinfo("MatchSolution");
    bestinfo += PvlKeyword("Matcher", best->matcher()->name());
    bestinfo += PvlKeyword("MatchedPairs", toString(best->size()));
    bestinfo += PvlKeyword("Efficiency",  toString(quality.Average()));
    if ( quality.ValidPixels() > 1 ) {
      bestinfo += PvlKeyword("StdDevEfficiency",
                             toString(quality.StandardDeviation()));
    }

    if(log){
      log->addGroup(bestinfo);
    }


    // If a cnet file was entered, write the ControlNet file of the specified
    // type.  Note that it was created as an image-to-image network. Must make
    // adjustments if a ground network is requested.
    if ( ui.WasEntered("ONET") ) {
      ControlNet cnet;
      cnet.SetNetworkId(ui.GetString("NETWORKID"));
      cnet.SetUserName(Isis::Environment::userName());
      cnet.SetDescription(best->matcher()->name());
      cnet.SetCreatedDate(findfeatures_runtime);
      QString target = ( ui.WasEntered("TARGET") ) ? ui.GetString("TARGET") :
                                                     best->target();
      cnet.SetTarget( target );
      ID pointId( ui.GetString("POINTID"), ui.GetInteger("POINTINDEX") );
      matcher.setDebugOff();
      PvlGroup cnetinfo = matcher.network(cnet, *best, pointId);

      if ( cnet.GetNumPoints() <= 0 ) {
        QString mess = "No control points found!!";
        logger->dbugout() << mess << "\n";
        throw IException(IException::User, mess, _FILEINFO_);
      }

      // Umm..have to check this. Probably only makes sense with two images
      if ( ui.GetString("NETTYPE").toLower() == "ground" ) {
        cnetinfo += PvlKeyword("SpecialNetType", "Ground");
        for ( int i = 0 ; i < cnet.GetNumPoints() ; i++ ) {
          ControlPoint *point = cnet.GetPoint(i);
          point->SetType(ControlPoint::Fixed);
          point->Delete(matcher.query().id());
          point->SetRefMeasure(0);
        }
      }

      // Write out control network
      cnet.Write( ui.GetFileName("ONET") );
      if(log){
        log->addGroup(cnetinfo);
      }
    }

    // If user wants a list of matched images, write the list to the TOLIST filename
    if ( ui.WasEntered("TOLIST") ) {
      QLogger fout( QDebugLogger::create( ui.GetAsString("TOLIST"),
                                               (QIODevice::WriteOnly |
                                                QIODevice::Truncate) ) );
      fout.logger() << matcher.query().name() << "\n";
      MatcherSolution::MatchPairConstIterator mpair = best->begin();
      while ( mpair != best->end() ) {
        if ( mpair->size() > 0 ) {
           fout.logger() << mpair->train().source().name() << "\n";
        }
        ++mpair;
      }
    }


    // If user wants a list of failed matched images, write the list to the
    // TONOTMATCHED file in append mode (assuming other runs will accumulate
    // failed matches)
    if ( ui.WasEntered("TONOTMATCHED") ) {
      QLogger fout( QDebugLogger::create( ui.GetAsString("TONOTMATCHED"),
                                               (QIODevice::WriteOnly |
                                                QIODevice::Append) ) );
      MatcherSolution::MatchPairConstIterator mpair = best->begin();
      while ( mpair != best->end() ) {
        if ( mpair->size() == 0 ) {
           fout.logger() << mpair->train().source().name() << "\n";
        }
        ++mpair;
      }
    }

    return;
  }
}
+11 −0
Original line number Diff line number Diff line
#ifndef findfeatures_h
#define findfeatures_h

#include "UserInterface.h"
#include "Pvl.h"

namespace Isis{
  extern void findfeatures(UserInterface &ui, Pvl *log=nullptr);
}

#endif
+16 −359

File changed.

Preview size limit exceeded, changes collapsed.

+0 −4
Original line number Diff line number Diff line
BLANKS = "%-6s"    
LENGTH = "%-40s"

include $(ISISROOT)/make/isismake.tststree
+0 −31
Original line number Diff line number Diff line
APPNAME = findfeatures

include $(ISISROOT)/make/isismake.tsts

commands:
	$(LS) -1 $(INPUT)/Sub4-AS15-M-058[4-5]_msk.cub > $(OUTPUT)/apollo_nadir.lis;
	$(APPNAME) algorithm="brisk/brisk" \
	           match=$(INPUT)/Sub4-AS15-M-0583_msk.cub \
	           fromlist=$(OUTPUT)/apollo_nadir.lis \
	           tolist=$(OUTPUT)/toList.txt \
	           tonotmatched=$(OUTPUT)/unmatched.txt \
	           maxpoints = 5000 \
	           epitolerance=1.0 \
	           ratio=0.65 \
	           hmgtolerance=3.0 \
	           onet=$(OUTPUT)/apolloAS15_1583.net \
	           networkid="ApolloAS15_0583" \
	           pointid="AS15_0583_????" \
	           description="Test Apollo 15 images" \
	           debug=false \
	           >& /dev/null; 

	# Need to trim filepaths from TOLIST output
	$(SED) 's|.*/\([^/]*\)|\1|' $(OUTPUT)/toList.txt \
	       >& $(OUTPUT)/cubes.txt;
	$(SED) 's|.*/\([^/]*\)|\1|' $(OUTPUT)/unmatched.txt \
	       >& $(OUTPUT)/unmatchedTruth.txt;
	$(RM) $(OUTPUT)/toList.txt;
	$(RM) $(OUTPUT)/apollo_nadir.lis;
	$(RM) $(OUTPUT)/apolloAS15_1583.net;
	$(RM) $(OUTPUT)/unmatched.txt;
Loading