diff --git a/CHANGELOG.md b/CHANGELOG.md index 59279428f9..ac0de72e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,11 +35,20 @@ release. ## [Unreleased] + ### Changed - Removed the `.py` extention from the _isisdataeval_ tool `isisdata_mockup` for consistency and install it in $ISISROOT/bin; added the `--tojson` and `--hasher` option to _isisdata_mockup_ tool improve utility; updated the tool `README.md` documentation to reflect this change, removed help output and trimmed example results; fixed paths to test data in `make_isisdata_mockup.sh`. [#5163](https://github.com/DOI-USGS/ISIS3/pull/5163) +- Significantly refactored FASTGEOM processing in findfeatures to accommodate stability and functionality. The scope of the algorithm was taken out of the ImageSource class and isolated to support this feature. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Report better information regarding the behavior of findfeatures, FASTGEOM algorithms, and creation of the output network. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) ### Added - Added rclone to run dependencies in meta.yaml [#5183](https://github.com/DOI-USGS/ISIS3/issues/5183) +- Add new program option TONOGEOM to findfeatures that logs captures geometry errors in the FASTGEOM algorithm and records them to the file provided in this parameter. These images are excluded from the matching process. References [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Added a new Radial FASTGEOM transform mapping algorithm to findfeatures to address performance problems with the Grid algorithm. This is now the default algorithm if none are selected by the user (see the new GLOBALS parameter to specify the algorithm) [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Added new parameter GLOBALS to make parameterization of findfeatures behavior significantly easier and convenient. Wrote significant documentation for the parameter and provide several examples showing its use. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Added two new examples demonstrating/documenting the use of FASTGEOM algorithm, parameterization using GLOBALS and creation of a regional mosaic using findfeatures. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Added new option GEOMSOURCE=BOTH to findfeatures to check both the MATCH and FROM/FROMLIST images for valid control measure geometry to produce better networks and prevent downstream processing errors. Ignore points that end up with no valid measures (but can be retained with use of PreserveIgnoredControl via GLOBALS parameterization). [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Added new gtests for findfeatures that replaces all the old application tests. These tests are FunctionalTestFindfeaturesFastGeomDefault, FunctionalTestFindfeaturesFastGeomRadialConfig, FunctionalTestFindfeaturesFastGeomGridDefault and FunctionalTestFindfeaturesFastGeomGridConfig. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) ### Deprecated @@ -49,6 +58,10 @@ release. - Updated History constructor to check for invalid BLOB before copying History BLOB to output cube [#4966](https://github.com/DOI-USGS/ISIS3/issues/4966) - Updated photomet MinnaertEmpirical model to support photemplate-style PVL format [#3621](https://github.com/DOI-USGS/ISIS3/issues/3621) - Fixed gaussstretch segmentation fault error and refactored gaussstretch files/tests to use gtest [#5240](https://github.com/DOI-USGS/ISIS3/issues/5240) +- Fix matrix inversion errors in findfeatures due to bad FASTGEOM matrix transforms using a more robust implementation to detect these errors and throw exceptions. Images with these errors are captured and logged to the TONOTMATCHED file. Fixes [#4639](https://github.com/DOI-USGS/ISIS3/issues/4639) +- Fixed findfeatures use of projected mosaics with correct check for TargetName in the Mapping labels. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) +- Fixed a bug in the cnetwinnow test that did not clean/remove it during test runs. +- Fixed findfeatures instantiation and use of projection classes to correctly return geometry data from projected images and mosaics. [#4772](https://github.com/DOI-USGS/ISIS3/issues/4772) ## [8.0.0] - 2023-04-19 diff --git a/isis/appdata/templates/findfeatures/findfeatures_fastgeom_defaults.pvl b/isis/appdata/templates/findfeatures/findfeatures_fastgeom_defaults.pvl new file mode 100644 index 0000000000..c16aaec9b4 --- /dev/null +++ b/isis/appdata/templates/findfeatures/findfeatures_fastgeom_defaults.pvl @@ -0,0 +1,40 @@ +Group = FastGeomCommonParameters + # Defaults used for findfeatures FastGeom Algorithms + + # FastGeomAlgorithm = radial + FastGeomPoints = 25 + FastGeomTolerance = 3 + # GeomType = camera + + FastGeomQuerySampleTolerance = 0 + FastGeomQueryLineTolerance = 0 + FastGeomTrainSampleTolerance = 0 + FastGeomTrainLineTolerance = 0 + + FastGeomDumpMapping = false +EndGroup = FastGeomCommonParameters + + +Group = FastGeomGridParameters + # FastGeomAlgorithm = grid + + FastGeomGridStartIteration = 0 + # FastGeomGridStopIteration = 1 to N + FastGeomGridIterationStep = 1 + + FastGeomTotalGridIterations = 1 + + FastGeomGridSaveAllPoints = false +EndGroup = FastGeomGridParameters + + +Group = FastGeomRadialParameters + # FastGeomAlgorithm = radial + + FastGeomRadialSegmentLength = 25 + FastGeomRadialPointCount = 5 + FastGeomRadialPointFactor = 1.0 + + # FastGeomRadialSegments = N +EndGroup = FastGeomRadialParameters +End diff --git a/isis/src/control/apps/findfeatures/FastGeom.cpp b/isis/src/control/apps/findfeatures/FastGeom.cpp index 250a854de4..133231c17a 100644 --- a/isis/src/control/apps/findfeatures/FastGeom.cpp +++ b/isis/src/control/apps/findfeatures/FastGeom.cpp @@ -7,33 +7,40 @@ find files of those names at the top level of this repository. **/ /* SPDX-License-Identifier: CC0-1.0 */ +#include + #include +#include "Constants.h" +#include "Distance.h" #include "FastGeom.h" +#include "IException.h" #include "ImageTransform.h" +#include "Latitude.h" +#include "Longitude.h" +#include "SurfacePoint.h" namespace Isis { -/* Constructor */ +/** Constructor */ FastGeom::FastGeom() : m_fastpts(25), m_tolerance(1.0), m_geomtype("camera"), m_maxarea(3.0), m_parameters() { validate(m_geomtype); } -/* Construct with parameters */ +/** Construct with parameters */ FastGeom::FastGeom(const PvlFlatMap ¶meters) : m_fastpts(25), m_tolerance(1.0), m_geomtype("camera"), m_maxarea(3.0), m_parameters(parameters) { m_fastpts = toInt(m_parameters.get("FastGeomPoints", "25")); - m_tolerance = toDouble(m_parameters.get("FastGeomTolerance", "1.0")); + m_tolerance = toDouble(m_parameters.get("FastGeomTolerance", "3.0")); m_geomtype = m_parameters.get("GeomType", "camera").toLower(); - m_maxarea = toDouble(m_parameters.get("GeomScaleLimit", "3.0")); validate(m_geomtype); } -/* Construct with individual parameters to compute the fast geom transform */ +/** Construct with individual parameters to compute the fast geom transform */ FastGeom::FastGeom(const int maxpts, const double tolerance, const bool crop, const bool preserve, const double &maxarea) : m_fastpts(maxpts), m_tolerance(tolerance), m_geomtype("camera"), @@ -62,24 +69,140 @@ FastGeom::~FastGeom() { } * @param train Train image * @return ImageTransform* Pointer to FastGeom transform */ -ImageTransform *FastGeom::compute(MatchImage &query, MatchImage &train) { - // std::cout << "\nQuery: " << query.source().name() << "\n"; - // std::cout << "Train: " << train.source().name() << "\n"; +ImageTransform *FastGeom::compute(MatchImage &query, MatchImage &train, + QLogger logger) const { + + // Shortcut reference directly to output stream method + QDebugStreamType &logit = logger.logger(); + + logit << "\n++++ Running FastGeom ++++\n"; + logit << "*** QueryImage: " << query.source().name() << "\n"; + logit << "*** TrainImage: " << train.source().name() << "\n"; + + // Set default map to identity matrix + cv::Mat mapper = cv::Mat::eye(3,3,CV_64FC1); // Image description - RectArea qSize(0.0f, 0.0f, query.source().samples(), query.source().lines() ); - RectArea tSize(0.0f, 0.0f, train.source().samples(), train.source().lines() ); + RectArea qSize(0.0, 0.0, query.source().samples(), query.source().lines() ); + RectArea tSize(0.0, 0.0, train.source().samples(), train.source().lines() ); + + // SANITY CHECK + // Ensure both images have valid cameras + QStringList errors; + if ( !query.source().hasGeometry() ) { + errors.append("Query image (" + query.source().name() + + ") does not support geometry operations (no camera/projection)!"); + } + + if ( !train.source().hasGeometry() ) { + errors.append("Train image (" + train.source().name() + + ") does not support geometry operations (no camera/projection)!"); + } + + if ( errors.size() > 0 ) { + logit << "--> Failed: " << errors.join("\n") << "\n"; + throw IException(IException::User, "--> FastGeom failed <--\n" + errors.join("\n"), + _FILEINFO_); + } + + // Setup common processing parameters + double q_samps = query.source().samples(); + double q_lines = query.source().lines(); + + double t_samps = train.source().samples(); + double t_lines = train.source().lines(); + + // Now get FOV tolerances. Default maps strictly within detector boundaries. + double fg_q_sample_tolerance = toDouble(m_parameters.get("FastGeomQuerySampleTolerance", "0.0")); + double fg_q_line_tolerance = toDouble(m_parameters.get("FastGeomQueryLineTolerance", "0.0")); + double fg_t_sample_tolerance = toDouble(m_parameters.get("FastGeomTrainSampleTolerance", "0.0")); + double fg_t_line_tolerance = toDouble(m_parameters.get("FastGeomTrainLineTolerance", "0.0")); + + // Train line/sample map restrictions + double q_min_samp = 0.5 - fg_q_sample_tolerance; + double q_max_samp = q_samps + 0.4999 + fg_q_sample_tolerance; + double q_min_line = 0.5 - fg_q_line_tolerance; + double q_max_line = q_lines + 0.4999 + fg_q_line_tolerance; + FGFov q_fov(q_min_samp, q_min_line, (q_max_samp-q_min_samp), (q_max_line-q_min_line)); + + double t_min_samp = 0.5 - fg_t_sample_tolerance; + double t_max_samp = t_samps + 0.4999 + fg_t_sample_tolerance; + double t_min_line = 0.5 - fg_t_line_tolerance; + double t_max_line = t_lines + 0.4999 + fg_t_line_tolerance; + FGFov t_fov(t_min_samp, t_min_line, (t_max_samp-t_min_samp), (t_max_line-t_min_line)); + + // Select the newer radial or earlier grid method. You gotta choose the + // Grid option correctly or you will get the radial algorithm + QString fg_algorithm = m_parameters.get("FastGeomAlgorithm", "Radial").toLower(); + bool radial_method = ( "grid" != fg_algorithm ); + logit << " FastGeomAlgorithm: " << fg_algorithm << "\n"; + logit << " FastGeomPoints: " << m_fastpts << "\n"; + logit << " FastGeomTolerance: " << m_tolerance << "\n"; + logit << " FastGeomQuerySampleTolerance: " << fg_q_sample_tolerance << "\n"; + logit << " FastGeomQueryLineTolerance: " << fg_q_line_tolerance << "\n"; + logit << " FastGeomTrainSampleTolerance: " << fg_t_sample_tolerance << "\n"; + logit << " FastGeomTrainLineTolerance: " << fg_t_line_tolerance << "\n\n"; + + // Only the good points to matrix solver + std::vector q_infov_points, t_infov_points; + int n_total_points = 0; + + // Run the requested algorithm + if ( radial_method ) { + + n_total_points = radial_algorithm(query, train, q_fov, t_fov, m_parameters, + q_infov_points, t_infov_points, logger); + + } + else { // grid_method + + n_total_points = grid_algorithm(query, train, q_fov, t_fov, m_parameters, + q_infov_points, t_infov_points, logger); + + } + + //// Latitude/Longitude mapping complete! Check status + + // Log results + logit << "\n==> Geometric Correspondence Mapping complete <==\n"; + logit << " TotalPoints: " << n_total_points << "\n"; + logit.flush(); + + // Compute homography if enough point ater in common FOVs of both images, + // otherwise we report failure + if ( n_total_points < m_fastpts ) { + QString mess = "Failed to get FOV geometry mapping for " + train.name() + + " to " + query.name() + " needing " + QString::number(m_fastpts) + + " but got " + QString::number(n_total_points) +" in train FOV."; + logit << ">>> ERROR - " << mess << "\n"; + throw IException(IException::Programmer, mess, _FILEINFO_); + } + + // Compute homography tranformation. Note the order of the point arrays. + // This method computes the train to query transform. The inverse will + // provide sample,line translations from query-to-train sample,line + // coordinates + std::vector t_inliers; + mapper = getTransformMatrix(t_infov_points, q_infov_points, t_inliers, m_tolerance, logger); - // std::cout << "Train-to-query mapping...\n"; - cv::Mat t_to_q = train.source().getGeometryMapping(query.source(), - m_fastpts, - m_tolerance); + // Report the transformation matrix + logit << "\n MatrixTransform: \n"; + for (int i = 0 ; i < mapper.rows ; i++) { + logit << " "; + QString comma(""); + for (int j = 0 ; j < mapper.cols ; j++) { + logit << comma << mapper.at(i,j); + comma = ","; + } + logit << "\n"; + } + logit << "\n"; // The above matrix is for simply computing the direct fastgeom of the // training image into the image space of the query image, just like // cam2cam does. // - // Now consider cropping to only the mininmum common coverage or + // Now consider cropping to only the minimum common coverage or // preserving all the train image in the transformation (this option can // be really big and is not recommended for some situations!). @@ -89,49 +212,542 @@ ImageTransform *FastGeom::compute(MatchImage &query, MatchImage &train) { if ( "map" == m_geomtype ) { // Compute preserved image output size and translation matrix cv::Mat tMat; - RectArea tSizeFull = ImageTransform::transformedSize(t_to_q, + RectArea tSizeFull = ImageTransform::transformedSize(mapper, tSize.size(), tMat); - // std::cout << "FullTrain: " << tSizeFull << "\n"; // If it is within the area tolerance use it, otherwise just use the // same size as the output image below. if ( tSizeFull.area() < (m_maxarea * qSize.area()) ) { - // std::cout << "Use direct full mapping of Train-to-Query...\n"; - fastg.reset(new GenericTransform("FastGeomMap", t_to_q, + fastg.reset(new GenericTransform("FastGeomMap", mapper, tSizeFull)); } } else if ( "crop" == m_geomtype ) { // Crop train image to only common area in query image using inverse - // std::cout << "Compute train to query BB...\n"; - RectArea qbbox = ImageTransform::boundingBox( t_to_q.inv(), qSize, - tSize.size()); - // std::cout << "TrainBB: " << qbbox << "\n"; + RectArea qbbox = ImageTransform::boundingBox( GenericTransform::computeInverse(mapper), + qSize, + tSize.size()); // std::cout << "Compute query to train BB...\n"; - RectArea tbbox = ImageTransform::boundingBox( t_to_q, qbbox, + RectArea tbbox = ImageTransform::boundingBox( mapper, qbbox, qSize.size()); - // std::cout << "MapBB: " << tbbox << "\n"; - fastg.reset(new GenericTransform("FastGeomCrop", t_to_q, tbbox)); + fastg.reset(new GenericTransform("FastGeomCrop", mapper, tbbox)); } // If a mapper has not been allocated, allocate mapping to query image size // (as cam2cam does). if ( fastg.isNull() ) { - // std::cout << "FastGeom cam2map map created...\n"; - fastg.reset(new GenericTransform("FastGeomCamera", t_to_q, qSize.size())); + fastg.reset(new GenericTransform("FastGeomCamera", mapper, qSize.size())); } return ( fastg.take() ); } -void FastGeom::apply(MatchImage &query, MatchImage &train) { - // Add the fastggeom mapping transform - train.addTransform( compute(query, train) ); +/** + * @brief Radial algorithm to compute query/train geometric mapping correspondences + * + * This method computes a radial algorithm that can be configured by the user to + * compute geometric correspondence between the query and train images. This data + * will be used to compute a perspective homography matrix suitable to tranform the + * train image into the query image space for spatial comparisons. + * + * @param query Query image + * @param train Train image + * @param q_fov Query image detector FOV in lines/samples that could be + * expanded + * @param t_fov Train image detector FOV in lines/samples that could be + * expanded + * @param parameters Algorithm parameters that modifies processing + * @param q_infov_points Vector of line/sample image coordinates in query image + * @param t_infov_points Vector of line/sample image coordinates in train image + * that geometrically maps to the same coordinates in the + * query image + * @param logger Optional logging stream to report algorithm details + * @return int Total number of points in correspondence vectors + */ +int FastGeom::radial_algorithm(MatchImage &query, MatchImage &train, + const FastGeom::FGFov &q_fov, + const FastGeom::FGFov &t_fov, + const PvlFlatMap ¶meters, + std::vector &q_infov_points, + std::vector &t_infov_points, + QLogger logger) const { + + + // Shortcut reference directly to output stream method + QDebugStreamType &logit = logger.logger(); + + logit << "--> Using Radial Algorithm train-to-query mapping <--\n"; + + // Compute maximum radial track at the center of the image to + // the corner and scaling parameters + double fg_max_radius = std::sqrt( (q_fov.width * q_fov.width) + ( q_fov.height * q_fov.height) ) / 2.0; + double fg_radial_seglen = toDouble(parameters.get("FastGeomRadialSegmentLength", "25.0")); + double fg_point_count = toDouble(parameters.get("FastGeomRadialPointCount", "5.0")); + double fg_point_factor = toDouble(parameters.get("FastGeomRadialPointFactor", "1.0")); + + // Ensure the number of rings + if ( fg_radial_seglen <= 1.0 ) fg_radial_seglen = 1.5; + int fg_number_rings = std::ceil( fg_max_radius / fg_radial_seglen ); + + // See if user wants to secify the number of rings + if ( parameters.exists("FastGeomRadialSegments") ) { + fg_number_rings = std::ceil(toDouble(parameters.get("FastGeomRadialSegments"))); + } + + // Ensure a reasonable number of rings are computed + if ( fg_number_rings <= 1 ) fg_number_rings = 2; // Center and one ring + + // Got to be >= 3 or risk getting colinear points + if ( fg_point_count < 3) fg_point_count = 3; + + // Lets report what we got + logit << " FastGeomMaximumRadius: " << fg_max_radius << "\n"; + logit << " FastGeomRadialSegmentLength: " << fg_radial_seglen << "\n"; + logit << " FastGeomRadialPointCount: " << fg_point_count << "\n"; + logit << " FastGeomRadialPointFactor: " << fg_point_factor << "\n"; + logit << " FastGeomRadialSegments: " << fg_number_rings << "\n"; + logit.flush(); + + // Center of query image + double c_x = q_fov.width / 2.0; + double c_y = q_fov.height / 2.0; + + // Set up line/sample mapping correspondence between images + std::vector q_points, t_points; + std::vector q_surface_points; + std::vector t_inFOV; + + // Let's track some numbers + int n_total_points = 0; + int n_image_points = 0; + int n_mapped_points = 0; + int n_train_fov = 0; + + // Clear point correspondence arrays + q_infov_points.clear(); + t_infov_points.clear(); + + double ring_radius = 0.0; + for ( int ring = 0 ; ring < fg_number_rings ; ring++, ring_radius+=fg_radial_seglen ) { + + // Compute the number points / ring. First loop handles the degenerate case of + // a single point at the center of the image. + int rpoints = int(std::ceil( fg_point_count + ( fg_point_count * fg_point_factor) * + (double) (ring-1)) ); + if ( rpoints < 1 ) rpoints = 1; + + double d_theta = ( 360.0 / rpoints ); + + // Compute the points on the circle by d_theta increments + // double r_angle = d_theta / 2.0; + double r_angle = 0.0; + for ( int p = 0 ; p < rpoints ; p++, r_angle+=d_theta ) { + + // Potential point + n_total_points++; + + // Convert angle to radians and compute point on the unit circle at the + // current radius in a counterclockwise direction. + double theta = r_angle * DEG2RAD; + FGPoint q_coord( (c_x + (std::cos(theta) * ring_radius)), + (c_y - (std::sin(theta) * ring_radius)) ); + + // Check to see if the point is in the image FOV + if ( q_fov.contains(q_coord) ) { + n_image_points++; + + // Convert the point and if valid add to list + double t_x, t_y, t_r; + SurfacePoint q_surfpt = query.source().getLatLon(q_coord.y, q_coord.x); + if ( train.source().getLineSamp(q_surfpt, t_y, t_x, t_r) ) { + n_mapped_points++; + + // Query q_coord set above + FGPoint t_coord(t_x, t_y); + q_points.push_back(q_coord); + q_surface_points.push_back(q_surfpt); + t_points.push_back(t_coord); + + // Test for those falling in train FOV + bool inFOV = t_fov.contains(t_coord); + t_inFOV.push_back(inFOV); + if ( inFOV ) { + n_train_fov++; + + // Save the good points + q_infov_points.push_back(q_coord); + t_infov_points.push_back(t_coord); + } + + } + } + } + } + + // Log results + logit << "\n==> Radial Point Mapping complete <==\n"; + logit << " TotalPoints: " << n_total_points << "\n"; + logit << " ImagePoints: " << n_image_points << "\n"; + logit << " MappedPoints: " << n_mapped_points << "\n"; + logit << " InTrainMapFOV: " << n_train_fov << "\n"; + logit.flush(); + + // Dump the points. m_parameters will be used to determine if the + // data is to be written to a file. + dump_point_mapping(query, train, "radial", m_parameters, + q_points, t_points, q_surface_points, + t_inFOV, logger); + + return ( n_train_fov ); +} + +/** + * @brief Grid algorithm to compute query/train geometric mapping correspondences + * + * This method computes a grid algorithm that can be configured by the user to + * compute geometric correspondence between the query and train images. This data + * will be used to compute a perspective homography matrix suitable to tranform the + * train image into the query image space for spatial comparisons. + * + * @param query Query image + * @param train Train image + * @param q_fov Query image detector FOV in lines/samples that could be + * expanded + * @param t_fov Train image detector FOV in lines/samples that could be + * expanded + * @param parameters Algorithm parameters that modifies processing + * @param q_infov_points Vector of line/sample image coordinates in query image + * @param t_infov_points Vector of line/sample image coordinates in train image + * that geometrically maps to the same coordinates in the + * query image + * @param logger Optional logging stream to report algorithm details + * @return int Total number of points in correspondence vectors + */ +int FastGeom::grid_algorithm(MatchImage &query, MatchImage &train, + const FastGeom::FGFov &q_fov, + const FastGeom::FGFov &t_fov, + const PvlFlatMap ¶meters, + std::vector &q_infov_points, + std::vector &t_infov_points, + QLogger logger) const { + + // Shortcut reference directly to output stream method + QDebugStreamType &logit = logger.logger(); + + logit << "--> Using Grid Algorithm train-to-query mapping <--\n"; + + // Compute increment + int fg_minpts = qMax(m_fastpts, 16); + int increment = std::ceil(std::sqrt( qMax(24.0, (double) fg_minpts) )); + + double fg_max_axis = qMax( qMax(q_fov.width, q_fov.height), qMax(t_fov.width, t_fov.height) ); + int v_max_iter = int( fg_max_axis / 2.0 ); + + int fg_grid_start_iter = toInt( parameters.get("FastGeomGridStartIteration", "0") ); + int fg_grid_stop_iter = toInt( parameters.get("FastGeomGridStopIteration", toString(v_max_iter)) ); + int fg_grid_iter_step = toInt( parameters.get("FastGeomGridIterationStep", "1") ); + bool fg_save_all = toBool( parameters.get("FastGeomGridSaveAllPoints", "false") ); + + logit << " FastGeomGridStartIteration: " << fg_grid_start_iter << "\n"; + logit << " FastGeomGridStopIteration: " << fg_grid_stop_iter << "\n"; + logit << " FastGeomGridIterationStep: " << fg_grid_iter_step << "\n"; + logit << " FastGeomGridSaveAllPoints: " << toString(fg_save_all) << "\n"; + logit << " FastGeomPointIncrement: " << increment << "\n"; + + // Set up line/sample mapping correspondence between images + std::vector q_points, t_points; + std::vector q_surface_points; + std::vector t_inFOV; + + // Clear point correspondence arrays + q_infov_points.clear(); + t_infov_points.clear(); + + // Let's track some numbers + int n_total_points = 0; + int n_image_points = 0; + int n_mapped_points = 0; + int n_train_fov = 0; + + // Loop control + bool done = false; + int iteration = fg_grid_start_iter; + int currinc = 0; + int n_iterations = 0; + + // Density grid point loop. First interation=0 will produce just enough + // grid points (>=m_fastpts) + while ( (iteration < fg_grid_stop_iter) && ( !done) ) { + + currinc = increment + ( iteration * 2 ); + n_iterations++; + + double sSpacing = qMax( 1.0, q_fov.width/(currinc*1.0) ); + double lSpacing = qMax( 1.0, q_fov.height/(currinc*1.0) ); + + if ( qMax( sSpacing, lSpacing ) <= 1.0 ) done = true; // Last possible loop + + // Save all points computed if requested by user + if ( !fg_save_all ) { + q_points.clear(); + t_points.clear(); + t_inFOV.clear(); + q_surface_points.clear(); + } + + // Clear these for the matrix transform + q_infov_points.clear(); + t_infov_points.clear(); + n_train_fov = 0; + + // Run the grid spacing loop + for (int l = 0 ; l < currinc ; l++) { + for ( int s = 0 ; s < currinc ; s++ ) { + + // Total point count + n_total_points++; + + // Check to see if the point is in the image FOV + FGPoint q_coord( ((sSpacing / 2.0 + sSpacing * s + 0.5) + q_fov.x), + ((lSpacing / 2.0 + lSpacing * l + 0.5) + q_fov.y) ); + if ( q_fov.contains(q_coord) ) { + n_image_points++; + + // Convert the point and if valid add to list + double t_x, t_y, t_r; + SurfacePoint q_surfpt = query.source().getLatLon(q_coord.y, q_coord.x); + if ( train.source().getLineSamp(q_surfpt, t_y, t_x, t_r) ) { + n_mapped_points++; + + // Query q_coord set above + FGPoint t_coord(t_x, t_y); + q_points.push_back(q_coord); + q_surface_points.push_back(q_surfpt); + t_points.push_back(t_coord); + + // Test for those falling in train FOV + bool inFOV = t_fov.contains(t_coord); + t_inFOV.push_back(inFOV); + if ( inFOV ) { + n_train_fov++; + + // Save the good points + q_infov_points.push_back(q_coord); + t_infov_points.push_back(t_coord); + } + + } + } + } + } + + // If on this iteration we have enough points we are done + if ( n_train_fov >= m_fastpts ) done = true; + + // Next iteration + step + iteration += fg_grid_iter_step; + } + + // Log results + logit << "\n==> Grid Point Mapping complete <==\n"; + logit << " FastGeomTotalGridIterations: " << n_iterations << "\n"; + logit << " TotalPoints: " << n_total_points << "\n"; + logit << " ImagePoints: " << n_image_points << "\n"; + logit << " MappedPoints: " << n_mapped_points << "\n"; + logit << " InTrainMapFOV: " << n_train_fov << "\n"; + logit.flush(); + + // Dump the points. Method wil use the m_parameters to determine if the + // data is written to a file. + dump_point_mapping(query, train, "grid", m_parameters, + q_points, t_points, q_surface_points, + t_inFOV, logger); + + return ( n_train_fov ); + +} + +/** + * @brief Compute and apply the FastGeom matrix transformation + * + * This method computes and adds the FastGeom matrix transformation from + * the train image to the query image space. The underlying process may + * throw an error that would prevent the transform being added to the + * processing chain of the train image. Callers should trap these errors + * and handle as appropriate. + * + * @param query Query (MATCH) image + * @param train Train (FROM/FROMLIST) image + * @param logger Logging stream to report process + */ +void FastGeom::apply(MatchImage &query, MatchImage &train, QLogger logger) const { + // Add the fastgeom mapping transform + train.addTransform( compute(query, train, logger) ); + return; +} + +/** + * @brief Compute the transformation matrix from set of geometric mapping points + * + * This method will compute the transformation matrix from the set of + * corresponding geometric points in query and train images. See @compute(). + * + * @param querypts Sample/Line points in query image + * @param trainpts Sample/Line points in train image + * @param inliers Returns mask of points used in martrix computation. Values + * of false indicate an outlier + * @param tolerance Outlier tolerance in pixels + * @param logger Output stream to report any processing details + * @return cv::Mat The transform + */ +cv::Mat FastGeom::getTransformMatrix(const std::vector &querypts, + const std::vector &trainpts, + std::vector &inliers, + const double tolerance, + QLogger logger) { + + QDebugStreamType &logit = logger.logger(); + + logit << "\n--> Running Homography Image Transform <---\n"; + + logit << " IntialPoints: " << querypts.size() << "\n"; + logit << " Tolerance: " << tolerance << "\n"; + + // Wrap OpenCV code in try/catch for any errors that may occur + cv::Mat mapper; + try { + // Find homography using Least-Median robust method algorithm + inliers.assign(querypts.size(), 0); + mapper = cv::findHomography(querypts, trainpts, cv::LMEDS, tolerance, inliers); + + // Using the Least median method requires > 50% inliers. Check that here. + int nInliers(0); + for (unsigned int i = 0 ; i < inliers.size() ; i++) { + if ( 0 != inliers[i] ) { + nInliers++; + } + } + + logit << " TotalLmedsInliers: " << nInliers << "\n"; + + // If we have < 50% inliners, compute the RANSAC homography + double inlierPercent = ((double) nInliers / (double) querypts.size()) * 100.0; + logit << " PercentPassing: " << inlierPercent << "\n"; + + if ( 50.0 > inlierPercent ) { + logit << " LMEDS failed w/less than 50% inliers - computing RANSAC homography!\n"; + mapper = cv::findHomography(querypts, trainpts, cv::RANSAC, tolerance, inliers); + + int r_nInliers(0); + for (unsigned int i = 0 ; i < inliers.size() ; i++) { + if ( 0 != inliers[i] ) r_nInliers++; + } + logit << " TotalRansacInliers: " << r_nInliers << "\n"; + + } + + // Check for invalid/empty matrix which indicates OpenCV error + if ( mapper.empty() ) { + QString msg = "Error computing homography matrix"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + } + catch ( std::exception &e ) { + // This will also catch any ISIS error + QString msg = "Matrix transform error: " + QString(e.what()); + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + return (mapper); +} + + +/** Get parameters currently being used */ +PvlFlatMap FastGeom::getParameters() const { + return (m_parameters); +} + +/** + * @brief Dump correspondence mapping data to file + * + * This method will write the details of the geometric correspondence mapping + * computed by the grid or radial algorithm. The data is intended to produce + * all points computed including points in the query image that did not + * successfully map into the train image (t_inFOV). + * + * + * @param query Query image + * @param train Train image + * @param method Method (radial, grid) used to generate data. This + * string is added to the output file name + * @param parameters Algorithm parameters that modifies processing + * @param q_points Vector of all line/sample image coordinates in query image + * @param t_points Vector of all line/sample image coordinates in train image + * that geometrically maps to the same coordinates in the + * query image + * @param q_surface_points Surface point computed at all points. The latitude and + * longitudes of the points are reported if valid + * otherwise, NULLs are written. + * @param t_inFOV Boolean vector corresponding to valid mappings in + * point arrays. + * @param logger Optional logging stream to report algorithm details + */ +void FastGeom::dump_point_mapping(MatchImage &query, MatchImage &train, + const QString &method, const PvlFlatMap ¶meters, + const std::vector &q_points, + const std::vector &t_points, + const std::vector &q_surface_points, + const std::vector &t_inFOV, + QLogger logger) const { + + // Lets dump the data if requested + if ( toBool(m_parameters.get("FastGeomDumpMapping","False")) ) { + + // Shortcut reference directly to output stream method + QDebugStreamType &logit = logger.logger(); + + logit << "\n--> Dumping " << method << " points <---\n"; + + FileName q_file( query.name() ); + FileName t_file( train.name() ); + + QString csvout = q_file.baseName() + "_" + t_file.baseName() + "." + + method + ".fastgeom.csv"; + logit << " PointDumpFile: " << csvout << "\n"; + + // Open output file for writing + QDebugStream csvstrm = QDebugLogger::create( csvout, + (QIODevice::WriteOnly | + QIODevice::Truncate) ); + QDebugStreamType &csv(csvstrm->dbugout()); + + // Write the header + csv << "QuerySample,QueryLine,TrainSample,TrainLine," + << "Latitude,Longitude,Radius,X,Y,Z,InTrainFOV\n"; + + // Write the data + logit << " TotalPoints: " << q_points.size() << "\n"; + for ( unsigned int i = 0 ; i < q_points.size() ; i++) { + const SurfacePoint &srfpt( q_surface_points[i] ); + + csv << q_points[i].x << ", " << q_points[i].y << ", " + << t_points[i].x << ", " << t_points[i].y << ", " + << srfpt.GetLatitude().degrees() << ", " + << srfpt.GetLongitude().degrees() << ", " + << srfpt.GetLocalRadius().meters() << ", " + << srfpt.GetX().meters() << ", " + << srfpt.GetY().meters() << ", " + << srfpt.GetZ().meters() << ", " + << (t_inFOV[i] ? "True" : "False") << "\n"; + } + } + return; } diff --git a/isis/src/control/apps/findfeatures/FastGeom.h b/isis/src/control/apps/findfeatures/FastGeom.h index b795b98b2d..e070c70edc 100644 --- a/isis/src/control/apps/findfeatures/FastGeom.h +++ b/isis/src/control/apps/findfeatures/FastGeom.h @@ -19,6 +19,7 @@ find files of those names at the top level of this repository. **/ #include "GenericTransform.h" #include "MatchImage.h" #include "PvlFlatMap.h" +#include "QDebugLogger.h" namespace Isis { @@ -52,19 +53,62 @@ class ImageTransform; * @internal * @history 2015-10-01 Kris Becker - Original Version * @history 2016-04-06 Kris Becker Created .cpp file and completed documentation + * @history 2021-10-29 Kris Becker Added const qualifier to all compute() and + * apply() methods + * @history 2022-02-02 Kris Becker Added Grid implementation resulting in + * consolidation/abstraction of Radial + * algorithm code + * @history 2022-02-07 Kris Becker Modifications in response to USGS/Astro + * code review in PR #4772 */ class FastGeom { public: typedef GenericTransform::RectArea RectArea; + typedef cv::Point2d FGPoint; + typedef cv::Rect2d FGFov; + FastGeom(); FastGeom(const PvlFlatMap ¶meters); FastGeom(const int maxpts, const double tolerance, const bool crop = false, const bool preserve = false, const double &maxarea = 3.0); virtual ~FastGeom(); - ImageTransform *compute(MatchImage &query, MatchImage &train); - void apply(MatchImage &query, MatchImage &train); + // ImageTransform *compute(MatchImage &query, MatchImage &train) const; + ImageTransform *compute(MatchImage &query, MatchImage &train, + QLogger logger = QLogger() ) const; + + int radial_algorithm(MatchImage &query, MatchImage &train, + const FGFov &q_fov, const FGFov &t_fov, + const PvlFlatMap ¶meters, + std::vector &q_infov_points, + std::vector &t_infov_points, + QLogger logger = QLogger()) const; + + int grid_algorithm(MatchImage &query, MatchImage &train, + const FGFov &q_fov, const FGFov &t_fov, + const PvlFlatMap ¶meters, + std::vector &q_infov_points, + std::vector &t_infov_points, + QLogger logger = QLogger()) const; + + void apply(MatchImage &query, MatchImage &train, QLogger logger = QLogger() ) const; + + static cv::Mat getTransformMatrix(const std::vector &querypts, + const std::vector &trainpts, + std::vector &inliers, + const double tolerance, + QLogger logger = QLogger() ); + + PvlFlatMap getParameters() const; + + void dump_point_mapping(MatchImage &query, MatchImage &train, + const QString &method, const PvlFlatMap ¶meters, + const std::vector &q_points, + const std::vector &t_points, + const std::vector &q_surface_points, + const std::vector &t_inFOV, + QLogger logger = QLogger()) const; private: int m_fastpts; //!< Number of points to use for geom @@ -75,6 +119,7 @@ class FastGeom { PvlFlatMap m_parameters; //!< Parameters of transform void validate( const QString &geomtype ) const; + }; } // namespace Isis diff --git a/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.cpp b/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.cpp index 252f61858a..0a2cc7350b 100644 --- a/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.cpp +++ b/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.cpp @@ -206,6 +206,30 @@ const PvlFlatMap &FeatureAlgorithmFactory::globalParameters() const { } +/** + * @brief Parse global parameters into a Pvl flat map strucure + * + * This method will accept a string that contain variables structured according + * to specifications used in the algorithm string. It only recognizes + * keyword/variable structures of the form "keyword1:value1@keyword2:value2". + * + * @param globals String of global parameters + */ +PvlFlatMap FeatureAlgorithmFactory::parseGlobalParameters(const QString &globals) { + PvlFlatMap pvlmap; + QStringList parms = globals.split("@", QString::SkipEmptyParts); + for (int i = 0 ; i < parms.size() ; i++ ) { + + // Only parse substrings that have 2 distinct parts separated by : + QStringList parts = parms[i].split(":", QString::SkipEmptyParts); + if ( parts.size() == 2 ) { + pvlmap.add(parts[0], parts[1]); + } + } + + return ( pvlmap ); +} + /** * @brief Set the global parameters to use in all matchers created * @@ -391,7 +415,7 @@ RobustMatcherList FeatureAlgorithmFactory::create(const QString &specs, * * In addition, parameters that alter the behavior of the outlier detection * processing, among oother things, in the RobustMatcher can be specified as an - * additional part of the string using the "/paramters@name:value..." + * additional part of the string using the "/parameters@name:value..." * specification. * * @param definition A single string specification for an OpencCV feature-based diff --git a/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.h b/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.h index 6558375771..5140f10c85 100644 --- a/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.h +++ b/isis/src/control/apps/findfeatures/FeatureAlgorithmFactory.h @@ -95,6 +95,7 @@ namespace Isis { * @history 2019-05-16 Aaron Giroux & Eric Gault - Added a regular expression to * formatSpecifications method to allow for pathnames to be entered * using the savepath parameter. Fixes 2474. + * @history 2022-02-10 Kris Becker Added parseGlobalParamters() static method */ class FeatureAlgorithmFactory { public: @@ -105,6 +106,7 @@ class FeatureAlgorithmFactory { QStringList getListAll() const; + static PvlFlatMap parseGlobalParameters(const QString &globals); void setGlobalParameters(const PvlFlatMap &globals); void addGlobalParameters(const PvlFlatMap &globals); void addParameter(const QString &name, const QString &value); diff --git a/isis/src/control/apps/findfeatures/GenericTransform.cpp b/isis/src/control/apps/findfeatures/GenericTransform.cpp index 5837c96877..1206e90e18 100644 --- a/isis/src/control/apps/findfeatures/GenericTransform.cpp +++ b/isis/src/control/apps/findfeatures/GenericTransform.cpp @@ -6,10 +6,12 @@ find files of those names at the top level of this repository. **/ /* SPDX-License-Identifier: CC0-1.0 */ +#include #include #include #include #include "GenericTransform.h" +#include "IException.h" namespace Isis { /** Generic constructor is simply an identity transform */ @@ -62,7 +64,7 @@ cv::Mat GenericTransform::getMatrix() const { /** Return inverse transform matrix */ cv::Mat GenericTransform::getInverse() const { - return ( m_inverse); + return ( m_inverse ); } /** Return the resulting size of the transformed image */ @@ -120,6 +122,45 @@ cv::Point2f GenericTransform::inverse(const cv::Point2f &point) const { return ( dst[0] ); } +/** + * @brief Compute inverse with validation + * + * OpenCV may silenty return an empty matrix using the inv() method. There is a + * safer method that is applied here that will test if the matrix is invertable. + * If its not invertable and verify == true, then an exception is thrown. + * + * @internal + * @author 2021-10-03 Kris J. Becker + * @history 2022-02-07 Kris Becker Modifications in response to code review + * + * @param matrix Square matrix to invert + * @param verify If true will throw an exception if not invertable unless + * an actual error does occur, which throws unconditionally + * + * @return cv::Mat Inverted matrix + */ +cv::Mat GenericTransform::computeInverse(const cv::Mat &matrix, + const bool verify) { + cv::Mat inverse; + try { + double result = cv::invert(matrix, inverse); + if ( verify ) { + if ( 0.0 == result ) { + QString msg = "Transformation matrix is not invertable"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + } + } + catch ( std::exception &e ) { + // This will also catch any ISIS error + QString msg = "Matrix inversion error: " + QString(e.what()); + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + return ( inverse ); +} + + /** Set the forward matrix */ void GenericTransform::setMatrix(const cv::Mat &matrix) { m_matrix = matrix; @@ -132,14 +173,21 @@ void GenericTransform::setInverse(const cv::Mat &matrix) { return; } -/** Calculate the inverse transform from the forward matrix */ -cv::Mat GenericTransform::calculateInverse(const cv::Mat &matrix) { - if (matrix.empty()) { // inverting an empty matrix causes a segfault - QString msg = "Can't invert empty matrix."; - throw IException(IException::Programmer, msg, _FILEINFO_); - } - - return ( matrix.inv() ); +/** + * @brief Calculate the inverse transform from the forward matrix + * + * This method will compute the inverse of the matrix and return the result. If + * the matrix is not invertable an exception is thrown unless verify == false. + * If verify == false, it is up to the caller to test the returned matrix (which + * may be filled with 0's). + * + * @param matrix Matrix to invert + * + * @return cv:Mat Inverted matrix + */ +cv::Mat GenericTransform::calculateInverse(const cv::Mat &matrix, + const bool verify) { + return ( computeInverse(matrix, verify) ); } /** Set the size of the transformed image */ diff --git a/isis/src/control/apps/findfeatures/GenericTransform.h b/isis/src/control/apps/findfeatures/GenericTransform.h index 650a8b4242..176232ff91 100644 --- a/isis/src/control/apps/findfeatures/GenericTransform.h +++ b/isis/src/control/apps/findfeatures/GenericTransform.h @@ -31,6 +31,11 @@ namespace Isis { * @history 2014-07-01 Kris Becker - Original Version * @history 2016-03-08 Kris Becker Created .cpp from header and completed * documentation + * @history 2021-10-30 Kris J. Becker Added verify parameter to + * calculateInverse() method; if verify==true, the + * matrix is tested if it is indeed + * invertable. + * @history 2022-02-07 Kris Becker Modifications in response to code review */ class GenericTransform : public ImageTransform { public: @@ -55,11 +60,17 @@ class GenericTransform : public ImageTransform { virtual cv::Point2f forward(const cv::Point2f &point) const; virtual cv::Point2f inverse(const cv::Point2f &point) const; + static cv::Mat computeInverse(const cv::Mat &matrix, + const bool verify = true); + protected: void setMatrix(const cv::Mat &matrix); void setInverse(const cv::Mat &matrix); - virtual cv::Mat calculateInverse(const cv::Mat &matrix); + + // Use in deriving clases for differnt behavior + virtual cv::Mat calculateInverse(const cv::Mat &matrix, + const bool verify = true); void setSize(const cv::Size &mSize); diff --git a/isis/src/control/apps/findfeatures/ImageSource.cpp b/isis/src/control/apps/findfeatures/ImageSource.cpp index 0704526697..2c78a7f4d0 100644 --- a/isis/src/control/apps/findfeatures/ImageSource.cpp +++ b/isis/src/control/apps/findfeatures/ImageSource.cpp @@ -92,7 +92,7 @@ bool ImageSource::hasCamera() const { QString ImageSource::getTargetName() const { if ( hasProjection() ) { PvlGroup mapping = m_data->m_projection->Mapping(); - return (mapping["Target"][0]); + return (mapping["TargetName"][0]); } else if ( hasCamera() ) { return ( m_data->m_camera->targetName() ); @@ -218,18 +218,20 @@ SurfacePoint ImageSource::getLatLon(const double &line, const double &sample) { // Check for projection first and translate if ( hasProjection() ) { - if ( m_data->m_projection->SetCoordinate(sample, line) ) { - double lat = m_data->m_projection->UniversalLatitude(); - double lon = m_data->m_projection->UniversalLongitude(); - double radius = m_data->m_projection->LocalRadius(lat); + TProjection *proj = m_data->projection(); + if ( proj->SetWorld(sample, line) ) { + double lat = proj->UniversalLatitude(); + double lon = proj->UniversalLongitude(); + double radius = proj->LocalRadius(lat); point.SetSphericalCoordinates(Latitude(lat, Angle::Degrees), Longitude(lon, Angle::Degrees), Distance(radius, Distance::Meters)); } } else if ( hasCamera() ) { - if ( m_data->m_camera->SetImage(sample, line) ) { - point = m_data->m_camera->GetSurfacePoint(); + Camera *camera = m_data->camera(); + if ( camera->SetImage(sample, line) ) { + point = camera->GetSurfacePoint(); } } @@ -256,19 +258,21 @@ bool ImageSource::getLineSamp(const SurfacePoint &point, // Check for projection first and translate if ( hasProjection() ) { - if ( m_data->m_projection->SetUniversalGround(lat, lon) ) { + TProjection *proj = m_data->projection(); + if ( proj->SetUniversalGround(lat, lon) ) { isGood = true; - line = m_data->m_projection->WorldY(); - samp = m_data->m_projection->WorldX(); - radius = m_data->m_projection->LocalRadius(); + line = proj->WorldY(); + samp = proj->WorldX(); + radius = proj->LocalRadius(); } } else if ( hasCamera() ) { - if ( m_data->m_camera->SetUniversalGround(lat, lon) ) { + Camera *camera = m_data->camera(); + if ( camera->SetUniversalGround(lat, lon) ) { isGood = true; - line = m_data->m_camera->Line(); - samp = m_data->m_camera->Sample(); - radius = m_data->m_camera->LocalRadius().meters(); + line = camera->Line(); + samp = camera->Sample(); + radius = camera->LocalRadius().meters(); } } @@ -311,7 +315,7 @@ cv::Mat ImageSource::getGeometryMapping(ImageSource &match, train.clear(); #if 0 - std::cout << "SSamp, NSamps: " << ssamp << ", " << nsamps << "\n"; + std::cout << "\nSSamp, NSamps: " << ssamp << ", " << nsamps << "\n"; std::cout << "SLine, NLines: " << sline << ", " << nlines << "\n"; std::cout << "SINC, LINC: " << sSpacing << ", " << lSpacing << "\n"; std::cout << "Increment: " << increment << "\n"; @@ -405,12 +409,11 @@ bool ImageSource::initGeometry(Cube &cube) { bool gotOne(false); try { if ( cube.isProjected() ) { - int ns, nl; - m_data->m_projection = ((TProjection *) ProjectionFactory::CreateForCube(*cube.label(), ns, nl, true) ); + m_data->m_projection = ProjectionFactory::CreateFromCube( *cube.label() ); gotOne = true; } - // Try camera also, independantly + // Try camera also, independently m_data->m_camera= ( CameraFactory::Create(cube) ); gotOne = true; } diff --git a/isis/src/control/apps/findfeatures/ImageSource.h b/isis/src/control/apps/findfeatures/ImageSource.h index cc5103e2db..926e718186 100644 --- a/isis/src/control/apps/findfeatures/ImageSource.h +++ b/isis/src/control/apps/findfeatures/ImageSource.h @@ -21,6 +21,7 @@ find files of those names at the top level of this repository. **/ #include "Camera.h" #include "Cube.h" #include "SurfacePoint.h" +#include "Projection.h" #include "TProjection.h" namespace Isis { @@ -39,6 +40,10 @@ namespace Isis { * @history 2014-07-01 Kris Becker - Original Version * @history 2016-02-08 Kris Becker - Changed call to ISIS Histogram class for * recent changes + * @history 2019-11-19 Kris Becker - Correctly return of target name from the + * Mapping group of projected images. Incorrect + * keyword Target used instead of TargetName + * @history 2023-08-03 Kris J. Becker - Use Projection rather than TProjection */ class ImageSource { @@ -88,6 +93,8 @@ class ImageSource { * @author 2014-07-01 Kris Becker * @internal * @history 2014-07-01 Kris Becker - Original Version + * @history 2023-08-03 Kris J. Becker Use Projection rather than + * TProjection; add getters */ class SourceData : public QSharedData { public: @@ -101,7 +108,7 @@ class ImageSource { m_projection(0), m_camera(0), m_image(image), m_mutex(new QMutex()) { } SourceData(const QString &name, const QString &serialno, - TProjection *proj, Camera *camera, const cv::Mat &image) : + Projection *proj, Camera *camera, const cv::Mat &image) : m_name(name), m_serialno(serialno), m_projection(proj), m_camera(camera), m_image(image), m_mutex(new QMutex()) { } @@ -119,11 +126,27 @@ class ImageSource { delete m_mutex; } + QString name() const { + return ( m_name ); + } + + QString serialnumber() const { + return ( m_serialno ); + } + + TProjection *projection() const { + return ( (TProjection *) m_projection ); + } + + Camera *camera() const { + return ( m_camera ); + } + // Data.... QString m_name; QString m_serialno; - TProjection *m_projection; + Projection *m_projection; Camera *m_camera; cv::Mat m_image; diff --git a/isis/src/control/apps/findfeatures/MatchMaker.cpp b/isis/src/control/apps/findfeatures/MatchMaker.cpp index d27e3f6626..574bd54899 100644 --- a/isis/src/control/apps/findfeatures/MatchMaker.cpp +++ b/isis/src/control/apps/findfeatures/MatchMaker.cpp @@ -32,6 +32,7 @@ find files of those names at the top level of this repository. **/ #include "QDebugLogger.h" #include "RobustMatcher.h" #include "Statistics.h" +#include "SurfacePoint.h" namespace Isis { @@ -104,6 +105,7 @@ MatchImage MatchMaker::getGeometrySource() const { } if ( Query == m_geomFlag ) return ( query() ); + if ( Both == m_geomFlag ) return ( query() ); if ( Train == m_geomFlag ) return ( train() ); return ( MatchImage() ); // Got none } @@ -145,7 +147,8 @@ MatcherSolutionList MatchMaker::match(const RobustMatcherList &matchers) { } - PvlGroup MatchMaker::network(ControlNet &cnet, const MatcherSolution &solution, + PvlGroup MatchMaker::network(ControlNet &cnet, + const MatcherSolution &solution, ID &pointMaker) const { @@ -179,14 +182,26 @@ MatcherSolutionList MatchMaker::match(const RobustMatcherList &matchers) { logger().flush(); } - // Create control network + // Create control network. This will transfer all points to the network + // and any ones that don't make will be deleted. int nPoints = 0; - int nBad = 0; + int nBadPoints = 0; + int nBadMeasures = 0; + bool preserve_ignored = toBool(m_parameters.get("PreserveIgnoredControl", "False")); Statistics pointStats; for (int i = 0 ; i < points.size() ; i++) { - if ( points[i] != 0 ) { - if ( points[i]->GetNumValidMeasures() > 1 ) { - pointStats.AddData((double)points[i]->GetNumValidMeasures()); + if ( (points[i] != 0) ) { + bool isValid = ( !points[i]->IsIgnored() ) && (points[i]->GetNumValidMeasures() > 1); + nBadMeasures += (points[i]->GetNumMeasures() - points[i]->GetNumValidMeasures()); + if ( preserve_ignored || isValid ) { + if ( isValid ) { + pointStats.AddData((double)points[i]->GetNumValidMeasures()); + } + else { + // Ensure the point is ignored + points[i]->SetIgnored( true ); + nBadPoints++; + } points[i]->SetId(pointMaker.Next()); cnet.AddPoint(points[i]); points[i] = 0; @@ -197,7 +212,7 @@ MatcherSolutionList MatchMaker::match(const RobustMatcherList &matchers) { // other place to do it. delete points[i]; points[i] = 0; - nBad++; + nBadPoints++; } } } @@ -205,23 +220,40 @@ MatcherSolutionList MatchMaker::match(const RobustMatcherList &matchers) { cnetinfo += PvlKeyword("ImagesMatched", toString(nImages) ); cnetinfo += PvlKeyword("ControlPoints", toString(nPoints) ); cnetinfo += PvlKeyword("ControlMeasures", toString(nMeasures) ); - cnetinfo += PvlKeyword("SingleMeasurePoints", toString(nBad) ); + cnetinfo += PvlKeyword("InvalidIgnoredPoints", toString(nBadPoints) ); + cnetinfo += PvlKeyword("InvalidIgnoredMeasures", toString(nBadMeasures) ); + cnetinfo += PvlKeyword("PreserveIgnoredControl", toString(preserve_ignored) ); if ( isDebug() ) { logger() << " Images Matched: " << nImages << "\n"; logger() << " ControlPoints created: " << nPoints << "\n"; logger() << " ControlMeasures created: " << nMeasures << "\n"; - logger() << " Excluded single measure points: " << nBad << "\n"; + logger() << " InvalidIgnoredPoints: " << nBadPoints << "\n"; + logger() << " InvalidIgnoredMeasures: " << nBadMeasures << "\n"; + logger() << " PreserveIgnoredControl " << toString(preserve_ignored) << "\n"; logger().flush(); } + // Report measure statistics + PvlKeyword mkey = PvlKeyword("ValidPoints", toString(pointStats.ValidPixels()) ); + mkey.addComment(" -- Valid Point/Measure Statistics ---"); + cnetinfo += mkey; + if ( isDebug() ) { + logger() << "\n -- Valid Point/Measure Statistics -- \n"; + logger() << " ValidPoints " << pointStats.ValidPixels() << "\n"; + } + if ( pointStats.ValidPixels() > 0 ) { cnetinfo += PvlKeyword("MinimumMeasures", toString(pointStats.Minimum()) ); cnetinfo += PvlKeyword("MaximumMeasures", toString(pointStats.Maximum()) ); + cnetinfo += PvlKeyword("AverageMeasures", toString(pointStats.Average()) ); cnetinfo += PvlKeyword("StdDevMeasures", toString(pointStats.StandardDeviation()) ); + cnetinfo += PvlKeyword("TotalMeasures", toString((int) pointStats.Sum()) ); if ( isDebug() ) { logger() << " MinimumMeasures: " << pointStats.Minimum() << "\n"; logger() << " MaximumMeasures: " << pointStats.Maximum() << "\n"; + logger() << " AverageMeasures: " << pointStats.Average() << "\n"; logger() << " StdDevMeasures: " << pointStats.StandardDeviation() << "\n"; + logger() << " TotalMeasures: " << (int) pointStats.Sum() << "\n"; logger().flush(); } } @@ -256,9 +288,11 @@ int MatchMaker::addMeasure(ControlPoint **cpt, const MatchPair &mpair, (*cpt)->SetRefMeasure(reference); // Set lat/lon if requested for Query image - if ( Query == m_geomFlag ) { - // What to do when it fails?? - (void) setAprioriLatLon(*(*cpt), *reference, mpair.query()); + if ( (Query == m_geomFlag) || (Both == m_geomFlag) ) { + // We'll set the reference to ignore if this fails + if ( !setAprioriLatLon(*(*cpt), *reference, mpair.query() ) ) { + reference->SetIgnored( true ); + } } nMade++; } @@ -298,8 +332,19 @@ int MatchMaker::addMeasure(ControlPoint **cpt, const MatchPair &mpair, // Set lat/lon if requested for train image if ( Train == m_geomFlag ) { - // What to do when it fails?? - (void) setAprioriLatLon(*(*cpt), *trainpt, mpair.train() ); + // If it fails, ignore the point + if ( !setAprioriLatLon(*(*cpt), *trainpt, mpair.train() ) ) { + (*cpt)->SetIgnored( true ); + } + } + else if ( Both == m_geomFlag ) { + // Check for valid ground mapping. + SurfacePoint latlon = getSurfacePoint( *trainpt, mpair.train() ); + + // If it fails, ignore the measure + if ( !latlon.Valid() ) { + trainpt->SetIgnored( true ); + } } nMade++; @@ -325,20 +370,26 @@ ControlMeasure *MatchMaker::makeMeasure(const MatchImage &image, return ( v_measure.take() ); } -bool MatchMaker::setAprioriLatLon(ControlPoint &point, - const ControlMeasure &measure, - const MatchImage &image) const { +SurfacePoint MatchMaker::getSurfacePoint( const ControlMeasure &measure, + const MatchImage &image) const { // Check if the source has geometry - if ( !image.source().hasGeometry() ) { return (false); } + if ( !image.source().hasGeometry() ) { return (SurfacePoint()); } // Compute lat/lon double samp = measure.GetSample(); double line = measure.GetLine(); SurfacePoint latlon = image.source().getLatLon(line,samp); - // std::cout << " Lat/Lon Coordinate: " << latlon.GetLatitude().degrees() << ", " - // << latlon.GetLongitude().degrees() << "\n"; - point.SetAprioriSurfacePoint(latlon); + return (latlon); +} + +bool MatchMaker::setAprioriLatLon(ControlPoint &point, + const ControlMeasure &measure, + const MatchImage &image) const { + SurfacePoint latlon = getSurfacePoint(measure, image); + if ( latlon.Valid() ) { // Only set if its valid + point.SetAprioriSurfacePoint(latlon); + } return ( latlon.Valid() ); } diff --git a/isis/src/control/apps/findfeatures/MatchMaker.h b/isis/src/control/apps/findfeatures/MatchMaker.h index 643befdad7..7f217eee23 100644 --- a/isis/src/control/apps/findfeatures/MatchMaker.h +++ b/isis/src/control/apps/findfeatures/MatchMaker.h @@ -29,21 +29,28 @@ namespace Isis { class ControlNet; class ControlPoint; class ControlMeasure; +class SurfacePoint; /** * @brief Container for a feature match pair/set of data sources * + * The GeometrySourceFlag determines the source of the ControlPoint + * latitude/longitude coordinate. If Both, then Query take precidence. The Both + * option will ensure the Query and all Train images have valid geometry. + * * * @author 2015-08-18 Kris Becker * @internal * @history 2015-08-18 Kris Becker - Original Version * @history 2015-09-29 Kris Becker - Had line/sample transposed when computing * apriori lat/lon + * @history 2021-10-30 Kris Becker Add Both enum to invoke error checking; + * added getSurfacePoint() method. */ class MatchMaker : public QLogger { public: - enum GeometrySourceFlag { None, Query, Train }; + enum GeometrySourceFlag { None, Query, Train, Both }; MatchMaker(); MatchMaker(const QString &name, const PvlFlatMap ¶meters = PvlFlatMap(), @@ -112,6 +119,8 @@ class MatchMaker : public QLogger { ControlMeasure *makeMeasure(const MatchImage &image, const int &keyindex, const QString &name = "ControlMeasure") const; + SurfacePoint getSurfacePoint(const ControlMeasure &measure, + const MatchImage &image) const; bool setAprioriLatLon(ControlPoint &point, const ControlMeasure &measure, const MatchImage &image) const; diff --git a/isis/src/control/apps/findfeatures/RobustMatcher.cpp b/isis/src/control/apps/findfeatures/RobustMatcher.cpp index d43872f191..fdc3ba0f4d 100644 --- a/isis/src/control/apps/findfeatures/RobustMatcher.cpp +++ b/isis/src/control/apps/findfeatures/RobustMatcher.cpp @@ -851,8 +851,8 @@ cv::Mat RobustMatcher::ransacTest(const std::vector& matches, return ( fundamental ); } - // extract the surviving (inliers) matches (only valid for CV_FM_RANSAC and - // CV_FM_LMEDS methods)!!!! + // extract the surviving (inliers) matches (only valid for cv::FM_RANSAC and + // cv::FM_LMEDS methods)!!!! std::vector::const_iterator itIn = inliers.begin(); std::vector::const_iterator itM = matches.begin(); for ( ;itIn != inliers.end(); ++itIn, ++itM) { diff --git a/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G.net.png b/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G.net.png new file mode 100644 index 0000000000..a4cd3af58e Binary files /dev/null and b/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G.net.png differ diff --git a/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G_findfeatures_small.png b/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G_findfeatures_small.png new file mode 100644 index 0000000000..dad99fe0ea Binary files /dev/null and b/isis/src/control/apps/findfeatures/assets/EW0211981114G_EW0242463603G_findfeatures_small.png differ diff --git a/isis/src/control/apps/findfeatures/assets/messenger_control_mosaic_comparison.jpg b/isis/src/control/apps/findfeatures/assets/messenger_control_mosaic_comparison.jpg new file mode 100644 index 0000000000..a650a3bda8 Binary files /dev/null and b/isis/src/control/apps/findfeatures/assets/messenger_control_mosaic_comparison.jpg differ diff --git a/isis/src/control/apps/findfeatures/assets/messenger_mdis_qmos.png b/isis/src/control/apps/findfeatures/assets/messenger_mdis_qmos.png new file mode 100644 index 0000000000..51520abb0e Binary files /dev/null and b/isis/src/control/apps/findfeatures/assets/messenger_mdis_qmos.png differ diff --git a/isis/src/control/apps/findfeatures/assets/messenger_qnet_control_result.jpg b/isis/src/control/apps/findfeatures/assets/messenger_qnet_control_result.jpg new file mode 100644 index 0000000000..48dd2e4510 Binary files /dev/null and b/isis/src/control/apps/findfeatures/assets/messenger_qnet_control_result.jpg differ diff --git a/isis/src/control/apps/findfeatures/findfeatures.cpp b/isis/src/control/apps/findfeatures/findfeatures.cpp index e5160f48e8..dc52526641 100644 --- a/isis/src/control/apps/findfeatures/findfeatures.cpp +++ b/isis/src/control/apps/findfeatures/findfeatures.cpp @@ -15,6 +15,7 @@ find files of those names at the top level of this repository. **/ #include #include #include +#include // OpenCV stuff #include "opencv2/core.hpp" @@ -24,6 +25,7 @@ find files of those names at the top level of this repository. **/ #define HAVE_ISNAN +#include "Application.h" #include "Camera.h" #include "ControlMeasure.h" #include "ControlMeasureLogData.h" @@ -67,7 +69,16 @@ find files of those names at the top level of this repository. **/ #include "Pvl.h" using namespace std; -namespace Isis{ +namespace Isis { + + inline PvlGroup pvlmap_to_group( const PvlFlatMap &pvlmap, const QString &grpnam ) { + PvlGroup pgrp( grpnam ); + for ( auto pkey : pvlmap.values() ) { + pgrp.addKeyword( pkey ); + } + return ( pgrp ); + } + static void writeInfo(const QString &toname, Pvl &data, UserInterface &ui, Pvl *log) { if ( !toname.isEmpty() ) { @@ -88,12 +99,71 @@ namespace Isis{ return; } + /** + * @brief Loads train images and applies geometry if provided + * + * This function loads a trainer image and optionally applies geometry using + * a FastGeom transform. + * + * A MatchImage is added to the MatchMaker as a trainer image. YOU SHOULD NOT + * USE THIS FUNCTION TO SET THE QUERY IMAGE. Once loaded, a FastGeom is + * optionally applied to the image before adding it to the matcher. + * + * Errors are trapped here if the file cannot be loaded or the geometry proess + * in FastGeom fails. In this case, false is returned without adding the image + * to the matcher. + * + * @author 2021-10-29 Kris J. Becker + * @history 2022-02-08 Kris J. Becker Clarified parameter description and added + * explanation of return function return condition; modified + * return logic + * + * @param matcher The source reponsible for managing the matcher process + * @param trainfile File name to load + * @param fastgeom Apply FastGeom if provided + * + * @return bool Any errors encountered will return false without adding + * the train image to the matcher + */ + static bool load_train_with_geom(MatchMaker &matcher, const QString trainfile, + QDebugStream &logger, + const FastGeom *fastgeom = 0) { + + try { + MatchImage t_image = MatchImage( ImageSource(trainfile) ); + + // Compute a FastGeom transform if requested + if ( fastgeom ) { + // This will typically throw an exception for bad geometry. + // Will return false with no addition to matcher. + + // Note if FastGeom is successful, it adds a transform on + // the trainer that computes geometric relationships with + // the query image. + fastgeom->apply(matcher.query(), t_image, QLogger(logger)); + } + + // If alls good...expection should be thrown above otherwise + matcher.addTrainImage( t_image ); + } + catch ( IException &ie ) { + logger->dbugout() << "Failed to load " << trainfile << "\n\n"; + return ( false ); + } + + return ( true ); + } + void findfeatures(UserInterface &ui, Pvl *log) { // Program constants const QString findfeatures_program = "findfeatures"; - const QString findfeatures_version = "1.0"; - const QString findfeatures_revision = "$Revision$"; + const QString findfeatures_version = "1.2"; + const QString findfeatures_revision = "2023-06-21"; // Now is the date of revision! + // const QString findfeatures_runtime = Application::DateTime(); + + // Track runtime... + QTime runTime = QTime::currentTime(); // Get time for findfeatures_runtime time_t startTime = time(NULL); @@ -148,8 +218,15 @@ namespace Isis{ if ( ui.WasEntered("PARAMETERS") ) { QString pfilename = ui.GetAsString("PARAMETERS"); Pvl pfile(pfilename); - parameters.merge( PvlFlatMap(pfile) ); + PvlFlatMap parms = PvlFlatMap(pfile); + parameters.merge( parms ); parameters.add("ParameterFile", pfilename); + + // Log parameters loaded from the PARAMETERS file + if ( log ) { + auto parmgrp = pvlmap_to_group( parms, "Parameters"); + log->addLogGroup( parmgrp ); + } } // Get individual parameters if provided @@ -187,17 +264,38 @@ namespace Isis{ aspec.append( algos.join("|") ); } + // Now reset any global parameters provided by the user + if ( ui.WasEntered("GLOBALS") ) { + QString gblparms = ui.GetString("GLOBALS"); + PvlFlatMap globals = factory->parseGlobalParameters(gblparms); + factory->addGlobalParameters( globals ); + factory->addParameter("GLOBALS", gblparms); + + // Load values parsed from the GLOBALS string + if ( log ) { + auto globalgrp = pvlmap_to_group( globals, "Globals"); + log->addLogGroup( globalgrp ); + } + } + + // Now report the list of all global parameters in the pool + if ( log ) { + auto gpool = pvlmap_to_group( factory->globalParameters(), "GlobalParameterPool"); + log->addLogGroup( gpool ); + } // 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") ) ) { + if ( !( ui.WasEntered("MATCH") && ( ui.WasEntered("FROM") || + ui.WasEntered("FROMLIST") ) ) ) { return; } } @@ -223,41 +321,84 @@ namespace Isis{ //-------------Matching Business-------------------------- // Make the matcher class - MatchMaker matcher(ui.GetString("NETWORKID")); - matcher.setDebugLogger(logger, p_debug ); + MatchMaker matcher(ui.GetString("NETWORKID"), factory->globalParameters() ); + matcher.setDebugLogger( logger, p_debug ); + + // *** Set up fast geom processing *** + // 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); } + if ( "both" == geomsource ) { matcher.setGeometrySourceFlag(MatchMaker::Both); } - // Acquire query image - matcher.setQueryImage(MatchImage(ImageSource(ui.GetAsString("MATCH")))); + // Trap load errors. Maintain a bad geom/load file list + FileList badgeom; + try { - // Get the trainer images - if ( ui.WasEntered("FROM") ) { - matcher.addTrainImage(MatchImage(ImageSource(ui.GetAsString("FROM")))); - } + logger->dbugout() << "\nImage load started at " << Application::DateTime() << "\n"; - // 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()))); + // Check for FASTGEOM option + QScopedPointer fastgeom; + if ( ui.GetBoolean("FASTGEOM") ) { + fastgeom.reset( new FastGeom( factory->globalParameters() ) ); + } + + // Acquire query image. Do not use load_train_with_geom()!!! + matcher.setQueryImage(MatchImage(ImageSource(ui.GetAsString("MATCH")))); + + // Get the trainer image and apply geom if requested + if ( ui.WasEntered("FROM") ) { + QString tname = ui.GetAsString("FROM"); + if ( !load_train_with_geom(matcher, tname, logger, fastgeom.data()) ) { + badgeom.append(tname); + } } + + // If there is a list provided, get that too + if ( ui.WasEntered("FROMLIST") ) { + FileList trainers(ui.GetFileName("FROMLIST")); + BOOST_FOREACH ( FileName tfile, trainers ) { + if ( !load_train_with_geom(matcher, tfile.original(), logger, + fastgeom.data()) ) { + badgeom.append(tfile.original()); + } + } + } + + // Load complete + logger->dbugout() << "Image load complete at " << Application::DateTime() << "\n"; + + // Check load/geom status and report bad ones before potential + // app abort + if ( badgeom.size() > 0 ) { + logger->dbugout() << "\nTotal failed image loads/FastGeoms excluded: " << badgeom.size() << "\n"; + for ( int f = 0 ; f < badgeom.size() ; f++ ) { + logger->dbugout() << badgeom[f].toString() << "\n"; + } + + if ( ui.WasEntered("TONOGEOM") ) { + QString tonogeom(ui.GetAsString("TONOGEOM")); + badgeom.write(tonogeom); + logger->dbugout() << "\nSee also " << tonogeom << "\n\n"; + } + } + + // Force output to show progress + logger->flush(); + } + catch (IException &ie) { + QString msg = "Fatal load errors encountered"; + logger->dbugout() << "\n\n### " << msg << " - aborting..." << "\n"; + throw IException(ie, IException::Programmer, msg, _FILEINFO_); } // 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 ); + logger->dbugout() << "\n\n### No valid files loaded - aborting...\n"; + logger->dbugout() << "Time: " << Application::DateTime() << "\n"; + QString msg = "Input cubes (" + QString::number(badgeom.size()) + ") failed to load. " + + "Must provide valid FROM/FROMLIST and MATCH cube or image filenames"; + throw IException(IException::User, msg, _FILEINFO_); } // Check for Sobel/Scharr filtering options for both Train and Images @@ -297,7 +438,7 @@ namespace Isis{ logger->dbugout() << "Shucks! Insufficient matches were found (" << best->size() << ")\n"; QString mess = "Shucks! Insufficient matches were found (" + - QString::number(best->size()) + ")"; + QString::number(best->size()) + ")"; throw IException(IException::User, mess, _FILEINFO_); } @@ -306,10 +447,11 @@ namespace Isis{ PvlGroup bestinfo("MatchSolution"); bestinfo += PvlKeyword("Matcher", best->matcher()->name()); bestinfo += PvlKeyword("MatchedPairs", toString(best->size())); + bestinfo += PvlKeyword("ValidPairs", toString(quality.ValidPixels())); bestinfo += PvlKeyword("Efficiency", toString(quality.Average())); if ( quality.ValidPixels() > 1 ) { bestinfo += PvlKeyword("StdDevEfficiency", - toString(quality.StandardDeviation())); + toString(quality.StandardDeviation())); } if(log){ @@ -327,15 +469,22 @@ namespace Isis{ cnet.SetDescription(best->matcher()->name()); cnet.SetCreatedDate(findfeatures_runtime); QString target = ( ui.WasEntered("TARGET") ) ? ui.GetString("TARGET") : - best->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"; + + // Get total elapded time + QTime totalE(0,0); + totalE = totalE.addMSecs(runTime.msecsTo(QTime::currentTime())); + logger->dbugout() << "\nSession complete in " << totalE.toString("hh:mm:ss.zzz") + << " of elapsed time\n"; + throw IException(IException::User, mess, _FILEINFO_); } @@ -360,35 +509,49 @@ namespace Isis{ // 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::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"; + 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) + // TONOTMATCHED file if any are found if ( ui.WasEntered("TONOTMATCHED") ) { - QLogger fout( QDebugLogger::create( ui.GetAsString("TONOTMATCHED"), - (QIODevice::WriteOnly | - QIODevice::Append) ) ); + QStringList nomatches; + + // Search for unmatched files in the matcher network MatcherSolution::MatchPairConstIterator mpair = best->begin(); while ( mpair != best->end() ) { if ( mpair->size() == 0 ) { - fout.logger() << mpair->train().source().name() << "\n"; + nomatches.append( mpair->train().source().name() ); } ++mpair; } + + // Only write the output file if there are any unmatched image files + if ( nomatches.size() > 0) { + QLogger fout( QDebugLogger::create( ui.GetAsString("TONOTMATCHED"), + (QIODevice::WriteOnly | + QIODevice::Truncate) ) ); + for ( auto const &imgfile : nomatches ) { + fout.logger() << imgfile << "\n"; + } + } } + // Get total elapded time + QTime totalT(0,0); + totalT = totalT.addMSecs(runTime.msecsTo(QTime::currentTime())); + logger->dbugout() << "\nSession complete in " << totalT.toString("hh:mm:ss.zzz") + << " of elapsed time\n"; + return; } -} +} \ No newline at end of file diff --git a/isis/src/control/apps/findfeatures/findfeatures.xml b/isis/src/control/apps/findfeatures/findfeatures.xml index 7f7e3f7577..aa81cb0cd2 100644 --- a/isis/src/control/apps/findfeatures/findfeatures.xml +++ b/isis/src/control/apps/findfeatures/findfeatures.xml @@ -3,431 +3,467 @@ - Feature-based matching algorithms used to create ISIS control networks + Feature-based matching algorithms used to create ISIS control networks -

Introduction

-

- findfeatures was developed to provide an alternative approach to - create image-based ISIS control point networks. Traditional ISIS - control networks are typically created using equally spaced grids and - area-based image matching (ABM) techniques. Control points are at the - center of these grids and they are not necessarily associated with any - particular feature or interest point. - findfeatures applies feature-based matching (FBM) algorihms using - the full suite of OpenCV - - detection and descriptor extraction algorithms and - - descriptor matchers. The points detected by these - algorithms are associated with special surface features identified by - the type of detector algorithm designed to identify certain - charcteristics. Feature based matching has a - - twenty year history in computer vision and continues to benefit - from improvements and advancements to make development of applications - like this possible. -

-

- This application offers alternatives to traditional image matching options - such as autoseed, seedgrid and coreg. Applications - like coreg and pointreg are area-based matching, - findfeatures utilizes feature-based matching techniques. The - OpenCV feature matching framework is used - extensively in this application to implement the concepts contained in a - - robust feature matching algorithm - applied to overlapping single pairs or multiple overlapping image sets. -

-

Overview

-

- findfeatures uses OpenCV 3.1's - FBM algorithms to match a single image, or set of images, called the - trainer image(s) to a different image, called the query image. -

-

- Feature based algorithms are comprised of three basic processes: - detection of features (or keypoints), extraction of feature descriptors - and finally matching of feature descriptors from two image sources. - To improve the quality of matches, findfeatures applies a fourth - process, robust outlier detection. -

-

- Feature detection is the search for features of interest (or key - points) in an image. The ultimate goal is to find features that can - also be found in another image of the same subject or location. - Feature detection algorithms are often refered to as detectors. -

-

- Detectors describe key points based on their location in a specific - image. Feature descriptors allow features found by a detector to be - described outside of the context of that image. For example, features - could be described by their roundness or how much they contrast the - surrounding area. Feature descriptors provide a way to compare key - points from different images. Algorithms that extract feature - descriptors from keypoints in an image are called extractors. -

-

- The third process is to match key points from different images based on - their feature descriptors. The trainer images can be matched to the - query image (called a trainer to query match) or the query image can be - matched to the trainer images (called a query to trainer match). - findfeatures performs both a trainer to query match and a query - to trainer match. Algorithms for matching feature descriptors are - called matchers. -

-

- The final step is to remove outlier matches. This helps improve the - quality and accuracy of matches when conditions make matching - challenging. findfeatures uses a four step outlier removal - process. At each step, matches are rejected and only the surviving - matches are used in the following step. -

-
    -
  1. A ratio test comparing the best and second best match for each - point removes points that not sufficiently distinct.
  2. -
  3. The matches are checked for symmetry. If a match was made in the - trainer to query match, but not in the query to trainer match - (or vice versa) then the match is removed.
  4. -
  5. The fundamental matrices between the trainer images and the query - image are computed using the RANSAC algorithm and matches that - exceed the RANSAC tolerance are removed.
  6. -
  7. The homography matrices (projections from one image's perspective - into anothers) from the query image to the trainer images are - computed using the RANSAC algorithm and matches with a high - residual are removed.
  8. -
-

- Matches that survive the outlier rejection process are converted into - an output control network. From here, multiple control networks created - by systematic use of findfeatures can be combined into one - regional or global control network with cnetcombinept. This can - result in extremely large control networks. cnetthinner can be - used to reduce the size of the network while maintaining sufficient - distribution for use with the jigsaw application. If the control - network is going to be used to create a DEM, then it should not be - thinned. -

-

Supported Image Formats

-

- findfeatures is designed to support many different image - formats. However, ISIS cubes with camera models provide the greatest - flexibility when using this feature matcher. ISIS cubes with geometry - can be effectively and efficiently matched by applying fast geometric - transforms that project all overlapping candidate images (referred to - as train images in OpenCV terminolgy) to the camera space of the - match (or truth) image (referred to as the query image in OpenCV - terminology). This single feature allows users to apply virtually all - OpenCV detector and extractor, including algorithms that are not scale - and rotation invariant. Other popular image formats are supported using - OpenCV - - imread() image reader API. Images supported here can be provided as - the image input files. However, these images will not have geometric - functionality so support for the fast geometric option is not - available to these images. As a consequence, FBM algorithms that are - not scale and rotation invarant are not recommended for these images - unless they are relatively spatially consistent. Nor can the point - geometries be provided - only line/sample coorelations will be - computed in these cases. -

-

- Note that all images are converted to 8-bit when read in. -

-

Specifying Algorithms and Robust Matcher Parameters

-

- Detectors, extractors, matchers, and robust matcher parameters are - specified by a specification string entered as the ALGORITHM - parameter. The basic scheme is shown below (optional portions are - surrounded by [ ]). -

- - detector[@param1:value1@...]/extractor[@param1:value@...][/matcher@param1@value1@...][/parameters@param1:value1@...] - -

- The specification string consists of between two and four components - separated by /. Each component consists of entries - separated by @. The first component of the specification - string, detector[@param1:value1@...], defines the - detector. The first entry is the name of the algorithm. The remaining - entries are separated by @ and define parameters for the - detector. The entries consist of the parameter name followed by - : and then the parameter value. After the detector - component is / and then the extractor component, - extractor[@param1:value@...] After the extractor - component is / and then the matcher component, - [matcher@param1@value1@...]. The extractor and matcher - components are formatted the same way as the detector component. The - final component of the specification string, - [/parameters@param1:value1@...] defines the robust matcher - parameters. The first entry is the word parameters. The - remaining entries consist of parameter name:value pairs, - just like the parameters in the algorithm components. -

-

- An alternative scheme for the specification string allows the - components to be in any order. Each component is formatted the same, - except the first entry in the detector, extractor, and (if specified) - matcher components begin with detector., - extractor., and matcher. respectively. For - example, the specification below would enable root sift in the outlier - detection, define a FAST detector, define a LATCH descriptor extractor, - and define a FlannBased matcher. -

- - extractor.LATCH@Bytes:16@HalfSSDSize:4/parameters@RootSift:true/matcher.FlannBasedMatcher/detector.FAST@Threshold:9@NonmaxSuppression:false - -

- Many FBM algorithms are designed to use a specific detector, extractor - pair with shared parameters (SIFT and SURF are good examples of this). - For these cases, the alternative specification scheme allows for the - detector and extractor to be defined in a single component with shared - parameters. To do this, begin the first entry with - feature2d.. For example, the following specification would - define a SIFT algorithm with 4 octave layers for both the detector and - extractor along with a brute force matcher using the L1 norm. -

- matcher.BFMatcher@NormType:Norm_L1/feature2d.SIFT@NOctaveLayers:4. -

- The minimum specification string consists of a detector name and an - extractor name. When no matcher is specified, a brute force matcher - with parameters tailored to the extractor is used. For example - SIFT/SIFT would result in SIFT being used for the - detector, SIFT being used for the extractor, and a brute force matcher - used for the matcher. If used with the alternatice specification - scheme, the detector and extractor can be defined in a single - component. So, the specification feature2d.SIFT defines - the exact same detector, extractor, and matcher as the previous - specification. -

-

- Multiple sets of FBM algorithms and robust matcher parameters can be - entered via the ALGOSPECFILE parameter. It takes a text file - (*.lis) with a specification on each line. For each specification, a - separate set of FBM algorithms and robust matcher parameters will be - created. Each set will be used to match the input images and the set - that produces the best matches will be used to create the output - control network. When the DEBUG and/or DEBUGLOG - parameters are used, the results from each set along with the quality - of the match will be output. -

-

- Each algorithm has default parameters that are - suited to most uses. The LISTALL parameter will list every - supported detector, extractor, and matcher algorithm along with its - parameters and default values. The LISTSPEC parameter will - output the results of the parsed specification string(s). A description - of every algorithm supported by findfeatures and if they can be - used as a detector, extractor, and/or matcher can be found in the - Algorithms table. -

-

- Descriptions of the robust matcher parameters and their default values - can be found in the Robust Matcher - Parameters table. -

-

Choosing Algorithms and Parameters

-

- Choosing effective algorithms and parameters is critical to successful - use of findfeatures. If a poor choice of algorithms and/or - parameters is made, findfeatures will usually complete, - but the computation time and/or output control network quality will - suffer. findfeatures supports all of the OpenCV 3.1 - - detectors, extractors, and - - matchers. Some algorithms work well in a wide range of scenarios - (SURF and SIFT are both well tested and very robust), while others are - highly specialized. The following section will help you successfully - determine which algorithms and parameters to use. -

-

- findfeatures gives users a wide range of options to adjust how - it works. Such broad power can be daunting when the user is unfamiliar - with FBM. The following are some rules-of-thumb to help make things a - little less daunting. First, when in doubt, trust the defaults. The - defaults in findfeatures are designed to be a good fit for a - wide range of scenarios. They are not a perfect fit for every situation - but a perfect fit usually is not required. The detector and extractor - do not default to a specific algorithm, but the SIFT algorithm is a - very robust algorithm that will produce a high quality output control - network for most situations. The majority of more modern algorithms are - focused on speed increases. Second, if possible, always use the - FASTGEOM parameter. The majority of problems when using FBM - arise from the trainer and query images not having the same geometry. - The FASTGEOM parameter completely eliminates these challenges. - Combining the FASTGEOM parameter with algorithms that are - designed for speed (the FAST descriptor and BRIEF extractor are good - options) will quickly produce a high quality control network. Finally, - if you are torn between a few options, use the LISTSPEC - parameter to test each of them and then only use the best result. When - the LISTSPEC parameter is used, findfeatures will - automatically determine which specification produced the best matches - and use it to create the output control net. -

-

- Different detectors search for different types of features. For - example, the FAST algorithm searches for corners, while blob detection - algorithms search for regions that contrast their surroundings. - When choosing which detector to use, consider the prominent features - in the image set. The FAST algorithm would work well for finding key - points on a linear feature, while a blob detection algorithm would work well - for finding key points in nadir images of a heavily cratered area. -

-

- When choosing an extractor there are two things to consider: the - invariance of the extractor and the size of the extracted descriptor. - Different extractors are invariant to (not affected by) different - transformations. For example, the SURF algorithm uses descriptors that - are invariant to rotation, while BRIEF feature descriptors are not. - In general, invariance to more transformations comes at a cost, bigger - descriptors. Detectors often find a very large number of key points in - each image. The amount of time it takes to extract and then compare all - of the resultant feature descriptors heavily depends upon the size of - the descriptor. So, more invariance usually means longer computation - times. For example, using the BRIEF extractor (which extracts very - small feature descriptors) instead of the SURF extractor (which has - moderately sized feature descriptors) provides an order of magnitude - speed increases for both extraction and matching. If your images are - from the similar sensors and under similar conditions, then an - extractor that uses smaller descriptors (BRISK, BRIEF, etc.) will be - faster and just as accurate as extractors that use larger, more robust - descriptors (SIFT, SURF, etc.). If your images are from very different - sensors (a spot spectrometer and a highly distorted framing camera, a - low resolution framing camera and a high resolution push broom camera, - etc.) or under very different conditions (very oblique and nadir, - opposing sun angles, etc.) then using an extractor with a more robust - descriptor will take longer but will be significantly more accurate - than using an extractor with a smaller descriptor. -

-

- findfeatures has two options for matchers: - brute force matching and a FLANN based matcher. The brute force matcher - attempts to match a key point with every key point in another image and - then pairs it with the closest match. This ensures a good match but can - take a long time for large numbers of images and key points. The FLANN - based matcher trains itself to find the approximate best match. It - does not ensure the same accuracy as the brute force matcher, but is - significantly faster for large numbers of images and key points. By - default findfeatures uses a brute force matcher with parameters - set based upon the extractor used. -

-

- Several parameters allow for fine tuning the outlier rejection process. - The RATIO parameter determines how distinct matches must be. A - ratio close to 0 will force findfeatures to consider only - un-ambiguous matches and reject a large number of matches. If many, - indistinct features are detected in each image, a low ratio will result - in a smaller, higher quality control network. If few, non-distinct - features are detected in each image, a high ratio will prevent the - control network from being too sparse. The EPITOLERANCE and - EPICONFIDENCE parameters control how outliers are found when the - fundamental matrices are computed. These parameters will have the - highest impact when the query and trainer images are stereo pairs. The - HMGTOLERANCE parameter controls how outliers are found after the - homography matrices are computed. This parameter will have the highest - impact when the query and trainer images have very different exterior - orientations. -

-

- Prior to FBM, findfeatures can apply several transformations to - the images. These transformations can help improve match quality in - specific scenarios. The FASTGEOM, GEOMTYPE, and - FASTGEOMPOINTS parameters allow for reprojection of the trainer - images into the query image's geometry prior to FBM. These parameters - can be used to achieve the speed increases of algorithms that are not - rotation and/or scale invariant (BRIEF, FAST, etc.) without loss of - accuracy. These parameters require that the trainer and query images - are ISIS cubes with geometry. For rotation and scale invariant algorithms - (SIFT, SURF, etc.), these parameters will have little to no - effect. The FILTER parameter allows for the application of - filters to the trainer and query images prior to FBM. The SOBEL - option will emphasize edges in the images. The Sobel filter can - introduce artifacts into some images, so the SCHARR option is - available to apply a more accurate filter. These filters allows for - improved detection when using an edge based detector (FAST, AGAST, - BRISK, etc.). If an edge based detector is not detecting - a sufficient number of key points or the key points are not - sufficienty distinct, these filters may increase the number of - successful matches. -

-

- The OpenCV methods used in the outlier rejection process have several - options that can be set along with the algorithms. The available - parameters are listed in the Robust - Matcher Parameters table. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

Introduction

+

+ findfeatures was developed to provide an alternative approach to + create image-based ISIS control point networks. Traditional ISIS + control networks are typically created using equally spaced grids and + area-based image matching (ABM) techniques. Control points are at the + center of these grids and they are not necessarily associated with any + particular feature or interest point. + findfeatures applies feature-based matching (FBM) algorithms using + the full suite of OpenCV + + detection and descriptor extraction algorithms and + + descriptor matchers. The points detected by these + algorithms are associated with surface features identified by + the type of detector algorithm designed to represent certain + characteristics. Feature based matching has a + + twenty year history in computer vision and continues to benefit + from improvements and advancements to make development of applications + like this possible. +

+

+ This application offers alternatives to traditional image matching options + such as autoseed, seedgrid and coreg. Applications + like coreg and pointreg are area-based matching, whereas + findfeatures utilizes feature-based matching techniques. The + OpenCV feature matching framework is used + extensively in this application to implement the concepts commonly found in + + robust feature matching algorithms and + applied to overlapping single pairs or multiple overlapping image sets. +

+

Overview

+

+ findfeatures uses OpenCV's + FBM algorithms to match a single image, or set of images, called the + trainer image(s) (FROM/FROMLIST) to a different image, called the query image + (MATCH). +

+

+ Feature based algorithms are comprised of three basic processes: + detection of features (or keypoints), extraction of feature descriptors + and finally matching of feature descriptors from two image sources. + To improve the quality of matches, findfeatures applies a fourth + process - robust outlier detection. +

+

+ Feature detection is the search for features of interest (or key + points) in an image. The ultimate goal is to find features that can + also be found in another image of the same object or location. + Feature detection algorithms are often refered to as detectors. +

+

+ Detectors describe key points based on their location in a specific + image. Feature descriptors allow features found by a detector to be + described outside of the context of that image. For example, features + could be described by their roundness or how much they contrast the + surrounding area. Feature descriptors provide a way to compare key + points from different images. Algorithms that extract feature + descriptors from keypoints in an image are called extractors. +

+

+ The third process is to match key points from different images based on + their feature descriptors. The trainer images can be matched to the + query image (called a trainer to query match) or the query image can be + matched to the trainer images (called a query to trainer match). + findfeatures performs both a trainer to query match and a query + to trainer match. Algorithms for matching feature descriptors are + called matchers. +

+

+ The final step is to remove outlier matches. This helps improve the + quality and accuracy of matches when conditions make matching + challenging. findfeatures uses a four step outlier removal + process. At each step, matches are rejected and only the surviving + matches are used in the following step. +

+
    +
  1. A ratio test comparing the best and second best match for each + point removes points that are not sufficiently distinct.
  2. +
  3. The matches are checked for symmetry. If a match was made in the + trainer to query match, but not in the query to trainer match + (or vice versa) then the match is removed.
  4. +
  5. The fundamental matrices between the trainer images and the query + image are computed using the RANSAC algorithm and matches that + exceed the RANSAC tolerance are removed.
  6. +
  7. The homography matrices (projections from one image's perspective + into another) from the query image to the trainer images are + computed using the RANSAC algorithm and matches with a high + residual are removed.
  8. +
+

+ Matches that survive the outlier rejection process are converted into + an output control network. From here, multiple control networks created + by systematic use of findfeatures can be combined into one + regional or global control network with cnetcombinept. This can + result in extremely large control networks. cnetthinner can be + used to reduce the size of the network while maintaining sufficient + distribution for use with the jigsaw application. If the control + network is going to be used to create a DEM, then it should not be + thinned. +

+

Supported Image Formats

+

+ findfeatures is designed to support many different image + formats. However, ISIS cubes with camera models provide the greatest + flexibility when using this feature matcher. ISIS cubes with geometry + can be effectively and efficiently matched by applying fast geometric + transforms that project all overlapping candidate images (referred to + as train images in OpenCV terminolgy) to the camera space of the + match (or truth) image (referred to as the query image in OpenCV + terminology). This single feature allows users to apply virtually all + OpenCV detector and extractor, including algorithms that are not scale + and rotation invariant. Other popular image formats are supported using + OpenCV + + imread() image reader API. Images supported here can be provided as + the image input files. However, these images will not have geometric + functionality so support for the fast geometric option is not + available to these images. As a consequence, FBM algorithms that are + not scale and rotation invarant are not recommended for these images + unless they are relatively spatially consistent. Nor can the point + geometries be provided - only line/sample correlations will be + computed in these cases. +

+

+ Note that all images are converted to 8-bit when read in. +

+

Specifications for Robust Feature Matching Algorithms

+

+ Robust matcher algorithms consist of detectors, extractors, and + matchers components and their parameters and are selected by a + specification string provided in the ALGORITHM parameter. + More that one matcher algorithm configuration can be provided in + a file, one algorithm per line, specified in the ALGOSPECFILE + parameter. The basic scheme is shown below (optional portions are + enclosed by [ ]). +

+
+ + detector[@param1:value1@...]/extractor[@param1:value@...][/matcher@param1:value1@...][/parameters@param1:value1@...] + +

+
+ The specification string consists of between two and four algorithm + matching components, such as detectors, extractors and matchers, + separated by /. Each algorithm component can also have + unique parameter entries separated by @. The first + algorithm component of the specification + string, detector[@param1:value1@...], defines the + detector. The first entry is the name of the algorithm. The remaining + entries are detector parameters separated by @ and define + values for the detector. The parameter specification consist of the + parameter name followed by : providing the parameter value. + After the detector algorithm and its parameters is / which + begins the extractor algorithm specification, + extractor[@param1:value@...]. Following the extractor + specification is another / and the matcher algorithm, + [matcher@param1:value1@...]. The extractor and matcher + specifications are formatted the same way as the detector component. The + final component of the specification string, + [/parameters@param1:value1@...] defines the robust matcher + parameters. The first entry is the word parameters. The + remaining entries consist of parameter name:value pairs, + just like the parameters in the algorithm specifications. +

+

+ An alternative scheme for the specification string allows the + components to be in any order. Each component is formatted the same, + except the first entry in the detector, extractor, and (if specified) + matcher components begin with detector., + extractor., and matcher. respectively. For + example, the specification below would enable root sift in the outlier + detection, define a FAST detector, a LATCH descriptor extractor, + and a FlannBased matcher. +

+
+ + extractor.LATCH@Bytes:16@HalfSSDSize:4/parameters@RootSift:true/matcher.FlannBasedMatcher/detector.FAST@Threshold:9@NonmaxSuppression:false + +

+
+ Many FBM algorithms are designed to use a specific detector, extractor + pair with shared parameters (SIFT and ORB are good examples of this). + For these cases, the alternative specification scheme allows for the + detector and extractor to be defined in a single component with shared + parameters. To do this, begin the first entry with + feature2d.. For example, the following specification would + define a SIFT algorithm with 4 octave layers for both the detector and + extractor along with a brute force matcher using the L1 norm. +

+
+ matcher.BFMatcher@NormType:Norm_L1/feature2d.SIFT@NOctaveLayers:4. +

+
+ The minimum specification string consists of a detector name and an + extractor name. When no matcher is specified, a brute force matcher + with parameters tailored to the extractor is used. For example + SIFT/SIFT would result in SIFT being used for the + detector, SIFT being used for the extractor, and a brute force matcher + used for the matcher. If used with the alternative specification + scheme, the detector and extractor can be defined in a single + component. So, the specification feature2d.SIFT defines + the exact same detector, extractor, and matcher as the previous + specification. +

+

+ Multiple sets of FBM algorithms and robust matcher parameters can be + entered via the ALGOSPECFILE parameter. It takes a text file + (*.lis) with a specification on each line. For each specification, a + separate set of FBM algorithms and robust matcher parameters will be + created. Each set will be used to match the input images and the set + that produces the best matches will be used to create the output + control network. When the DEBUG and/or DEBUGLOG + parameters are used, the results from each set along with the quality + of the match will be output. +

+

+ Each algorithm has default parameters that are + suited for most uses. The LISTALL parameter will list every + supported detector, extractor, and matcher algorithm along with its + parameters and default values. The LISTSPEC parameter will + output the results of the parsed specification string(s). A description + of every algorithm supported by findfeatures and if they can be + used as a detector, extractor, and/or matcher can be found in the + Algorithms table. +

+

+ Descriptions of the robust matcher parameters and their default values + can be found in the Robust Matcher + Parameters table. +

+
+

Choosing Feature Matching Algorithms

+

+ Choosing effective algorithms and parameters is critical to successful + use of findfeatures. If a poor choice of algorithms and/or + parameters is made, findfeatures will usually complete, + but excessive computation time and/or poor quality control network will + result. findfeatures supports all of the OpenCV + + detectors, extractors, and + + matchers. Some algorithms work well in a wide range of scenarios + (BRISK and SIFT are both well tested and very robust), while others are + highly specialized. The following section provides guidelines to + determine which algorithms and parameters to use. +

+

+ findfeatures gives users a wide range of options to alter + algorithms functionaility. Such a broad range of choices/combinations + can be intimidating for users who are unfamiliar with FBM. The following + are some suggestions to help make reasonable decision. First, + when in doubt, trust the defaults. The defaults in findfeatures + are designed to be a reasonable choice for a wide range of conditions. + They may not be suitable for every situation but special configurations + are usually not required to produce acceptable results. Defaults for + the detector and extractor algorithms are not provided, but the SIFT + algorithm is a very general, scale and rotation invariant, robust + algorithm that can produce high quality control networks for most + situations. The majority of more modern algorithms are focused on + speed and efficiency. +

+

+ If at all possible, always use the FASTGEOM parameter. The + majority of problems when using FBM arise when trainer and query images + have inconsistent spatial geometry and/or significant variations in + observing conditions during image acquisition. The FASTGEOM parameter + helps minimize these challenges by using a priori ephemeris SPICE data + to create and apply a perspective matrix warp projection using the + query and training geometry. Combining the FASTGEOM option with + algorithms that are designed for speed (the FAST descriptor and BRIEF + extractor are reasonable options) will quickly produce a control network. + Keep in mind these networks are whole-pixel accuracy and will likely + require refinement to subpixel accuracy with pointreg prior to + bundle adjustment with jigsaw. +

+

+ Different detectors search for different types of features. For + example, the FAST algorithm searches for corners, while blob detection + algorithms search for regions that contrast their surroundings. + When choosing which detector to use, consider the prominent features + in the image set. The FAST algorithm would work well for finding key + points on a linear feature, while a blob detection algorithm would work + well for finding key points in nadir images of a heavily cratered area. +

+

+ When choosing an extractor there are two things to consider: the + invariance of the extractor and the size of the extracted descriptor. + Different extractors are invariant (i.e, not adversely affected) to + different transformations. For example, the SIFT algorithm uses + descriptors that are invariant to rotation, while BRIEF feature + descriptors are best for images that are spatially similar in + orientation. In general, invariance to more transformations comes + at a cost, bigger descriptors. Detectors often find a very large + number of key points in each image. The amount of time it takes + to extract and then compare (i.e., match) the resultant feature + descriptors heavily depends upon the size of the descriptor. So, + more invariance usually means longer computation times. For + example, using the BRIEF extractor (which extracts very small + feature descriptors) instead of the SIFT extractor (which has + moderately sized feature descriptors) provides an order of magnitude + speed increases for both extraction and matching. If your images are + from similar sensors and under similar conditions, then an + extractor that uses smaller descriptors (BRISK, BRIEF, etc.) will be + faster and just as accurate as extractors that use larger, more robust + descriptors (SIFT, etc.). If your images are from very different + sensors (a line scanner matched to a highly distorted framing camera, a + low resolution framing camera and a high resolution push broom camera, + etc...) or under very different conditions (very oblique and nadir, + opposing sun angles, etc.) then using an extractor with a more robust + descriptor will take longer but will be significantly more accurate + than using an extractor with a smaller descriptor. +

+

+ findfeatures has two options for matchers: + brute force matching and a FLANN based matcher. The brute force matcher + attempts to match a key point with every key point in another image and + then pairs it with the closest match. This ensures a good match but can + take a long time for large numbers of images and key points. The FLANN + based matcher trains itself to find the approximate best match. It + does not ensure the same accuracy as the brute force matcher, but is + significantly faster for large numbers of images and key points. By + default findfeatures uses a brute force matcher with parameters + automatically chosen based upon the type of extractor used. +

+

+ Several parameters allow for fine tuning of the outlier rejection process. + The RATIO parameter determines how distinct matches must be. A + ratio close to 0 will force findfeatures to consider only + unambiguous matches and reject a large number of matches. If many, + indistinct features are detected in each image, a low ratio will result + in smaller, higher quality control networks. If few, non-distinct + features are detected in each image, a high ratio will prevent the + control network from being too sparse. The EPITOLERANCE and + EPICONFIDENCE parameters control how outliers are found when the + fundamental matrices are computed. These parameters will have the + highest impact when the query and trainer images are stereo pairs. The + HMGTOLERANCE parameter controls how outliers are found after the + homography matrices are computed. This parameter will have the highest + impact when the query and trainer images have very different exterior + orientations. When using FASTGEOM, it is recommended to use higher + values for these tolerances due to distortions that are introduced + by image size perspective warping using the homography matrix. +

+

+ Prior to FBM, findfeatures can apply several transformations to + the images. These transformations can help improve match quality in + specific scenarios. The FASTGEOM, GEOMTYPE, and + FASTGEOMPOINTS parameters allow for reprojection of the trainer + images into the query image's geometry prior to FBM. (This algorithm is + very similar to processing by the ISIS cam2cam application. + FASTGEOM differs in that it is not as robust or accurate as + cam2cam because FASTGEOM applies a single matrix to warp the + entire image - cam2cam uses a rubber sheet projection which is + significantly more robust and accurate but can take significantly + longer to project images.) These parameters can be used to achieve + the speed increases of algorithms that are not rotation and/or scale + invariant (BRIEF, FAST, etc.) without loss of accuracy. These + parameters require that the trainer and query images + be ISIS cubes with geometry/cartography capabilites established by + running the spiceinit application on all images. For rotation + and scale invariant algorithms (SIFT etc.), these parameters + may have very little or significant adverse effects. +

+

+ The FILTER parameter allows for the application of filters to + the trainer and query images prior to FBM. The SOBEL option + will emphasize edges in the images. The Sobel filter can introduce + artifacts into some images, so the SCHARR option is also + available to apply a more accurate filter. These filters allows for + improved detection when using edge based detectors (FAST, AGAST, BRISK, + etc...). If an edge based detector is not detecting a sufficient number + of key points or the key points are not sufficienty distinct, these + filters may increase the number of successful matches. +

+

+ The OpenCV methods used in the outlier rejection process have several + options that can be set along with the algorithms. The available + parameters for are robust matcher algorithms are listed in the following + Robust Matcher Parameters table. + Feature matching algorithms and their descriptions and references are + listed in the Algorithms table. + The LISTALL program option will list all available algorithms, + their feature capabilities, and parameters with default values. +

+
-

- Robust Matcher Parameters -

-
KeywordDefaultDescription
SaveRenderedImagesFalse - Option to save the images that are matched - after all transforms (e.g., fast geom, filtered, etc...) - have been applied. The query (MATCH) image will have - "_query" will be appended to the base name. All - FROM/FROMLIST images will have "_train" appended to their - names. They are saved as PNG images in the directory - specifed by the SavePath parameter. -
SavePath$PWD - Specify the directory path to save all transform rendered - images if SaveRenderedImages=TRUE. -
RootSiftFalse - Apply the - - RootSift algorithm to the descriptors that - normalizes SIFT-type of descriptors. A good description of - the application of this algorithm is described in - - this article. In general, SIFT descriptors histograms are - compared in Euclidean space. RootSift applies a Hellinger - kernel to the descriptor histograms that greatly improve - performance and still allows Euclidean distances in its - evaluation. Be sure to use this for SIFT-type descriptors - only. -
MinimumFundamentalPoints8 - The Epipolar algorithm in OpenCV requires a minimim of 8 - points in order to properly compute the fundamental matrix. - This parameter allows the user to specify the minimum number - of points allowed. -
RefineFundamentalMatrixTrue - A single computation of the fundamental matrix is performed - unless this parameter is set to true. In this case, a new - fundmental matrix is computed after outlier are detected and - removed. This will improve the matrix since outliers are - removed and the matrix is recomputed. -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -486,8 +522,8 @@ @@ -572,8 +608,8 @@ @@ -853,8 +889,8 @@ @@ -875,8 +911,8 @@
+

+ Robust Matcher Parameters +

+
KeywordDefaultDescription
SaveRenderedImagesFalse + Option to save the images that are matched + after all transforms (e.g., fast geom, filtered, etc...) + have been applied. The query (MATCH) image will have + "_query" will be appended to the base name. All + FROM/FROMLIST images will have "_train" appended to their + names. They are saved as PNG images in the directory + specifed by the SavePath parameter. +
SavePath$PWD + Specify the directory path to save all transform rendered + images if SaveRenderedImages=TRUE. +
RootSiftFalse + Apply the + + RootSift algorithm to the descriptors that + normalizes SIFT-type of descriptors. A good description of + the application of this algorithm is described in + + this article. In general, SIFT descriptors histograms are + compared in Euclidean space. RootSift applies a Hellinger + kernel to the descriptor histograms that greatly improve + performance and still allows Euclidean distances in its + evaluation. Be sure to use this for SIFT-type descriptors + only. +
MinimumFundamentalPoints8 + The Epipolar algorithm in OpenCV requires a minimim of 8 + points in order to properly compute the fundamental matrix. + This parameter allows the user to specify the minimum number + of points allowed. +
RefineFundamentalMatrixTrue + A single computation of the fundamental matrix is performed + unless this parameter is set to true. In this case, a new + fundmental matrix is computed after outlier are detected and + removed. This will improve the matrix since outliers are + removed and the matrix is recomputed. +
MinimumHomographyPoints Detector, Extractor The KAZE algorithm is a very robust algorithm that attempts to - provide improved invariance to scale. The method that SIFT and SURF + provide improved invariance to scale. The method that SIFT use to provide scale invariance can result in proliferation of noise. The KAZE algorithm employs a non-linear scaling method that helps prevent this. Hence, the KAZE algorithm provides very strong @@ -802,8 +838,8 @@ The LATCH algorithm extracts a fast binary descriptor that offers greater invariance than other binary descriptor extractors. It lies between the extremely fast binary descriptor extractors (BRIEF and - BRISK) and the very invariant but slow descriptor extractors (SURF - and SIFT). The LATCH algorithm also offers a CUDA implementation + BRISK) and the very invariant but slow descriptor extractors (SIFT). + The LATCH algorithm also offers a CUDA implementation that allows for extremely fast computation times when using multiple GPUs in parallel.
-

Using Debugging to Diagnose Behavior

-

- An additional feature of findfeatures is a detailed debugging - report of processing behavior in real time for all matching and - outlier detection algorithms. The data produced by this option is - very useful to identify the exact processing step where some matching - operations may result in failed matching operations. In turn, this - will allow users to alter parameters to address these issues to lead - to successful matches that would otherwise not be able to achieve. -

-

- To invoke this option, users set DEBUG=TRUE and provide an optional - output file (DEBUGLOG=filename) where the debug data is written. If - no file is specified, output defaults to the terminal device. Here is - an example (see the example section for details) of a debug session - with line numbers added for reference of the description that - follows: -

-
-  1 ---------------------------------------------------
-  2 Program:        findfeatures
-  3 Version         0.1
-  4 Revision:       $Revision: 7311 $
-  5 RunTime:        2017-01-03T16:59:01
-  6 OpenCV_Version: 3.1.0
-  7
-  8 System Environment...
-  9 Number available CPUs:     4
- 10 Number default threads:    4
- 11 Total threads:             4
- 12
- 13 Total Algorithms to Run:     1
- 14
- 15 @@ matcher-pair started on 2017-01-03T16:59:02
- 16
- 17 +++++++++++++++++++++++++++++
- 18 Entered RobustMatcher::match(MatchImage &query, MatchImage &trainer)...
- 19   Specification:   surf@hessianThreshold:100/surf/BFMatcher@NormType:NORM_L2@CrossCheck:false
- 20 **  Query Image:   EW0211981114G.lev1.cub
- 21        FullSize:     (1024, 1024)
- 22        Rendered:     (1024, 1024)
- 23 **  Train Image:   EW0242463603G.lev1.cub
- 24        FullSize:     (1024, 1024)
- 25        Rendered:     (1024, 1024)
- 26 --> Feature detection...
- 27   Total Query keypoints:    11823 [11823]
- 28   Total Trainer keypoints:  11989 [11989]
- 29   Processing Time:          0.307
- 30   Processing Keypoints/Sec: 77563.5
- 31 --> Extracting descriptors...
- 32   Processing Time(s):         0.9
- 33   Processing Descriptors/Sec: 26457.8
- 34
- 35 *Removing outliers from image pairs
- 36 Entered RobustMatcher::removeOutliers(Mat &query, vector<Mat> &trainer)...
- 37 --> Matching 2 nearest neighbors for ratio tests..
- 38   Query, Train Descriptors: 11823, 11989
- 39   Computing query->train Matches...
- 40   Total Matches Found:   11823
- 41   Processing Time:       1.906
- 42   Matches/second:        6203.04
- 43   Computing train->query Matches...
- 44   Total Matches Found:   11989
- 45   Processing Time:       2.412 <seconds>
- 46   Matches/second:        4970.56
- 47  -Ratio test on query->train matches...
- 48 Entered RobustMatcher::ratioTest(matches[2]) for 2 NearestNeighbors (NN)...
- 49   RobustMatcher::Ratio:       0.65
- 50   Total Input Matches Tested: 11823
- 51   Total Passing Ratio Tests:  988
- 52   Total Matches Removed:      10835
- 53   Total Failing NN Test:      10835
- 54   Processing Time:            0
- 55  -Ratio test on train->query matches...
- 56 Entered RobustMatcher::ratioTest(matches[2]) for 2 NearestNeighbors (NN)...
- 57   RobustMatcher::Ratio:       0.65
- 58   Total Input Matches Tested: 11989
- 59   Total Passing Ratio Tests:  1059
- 60   Total Matches Removed:      10930
- 61   Total Failing NN Test:      10930
- 62   Processing Time:            0
- 63 Entered RobustMatcher::symmetryTest(matches1,matches2,symMatches)...
- 64  -Running Symmetric Match tests...
- 65   Total Input Matches1x2 Tested: 988 x 1059
- 66   Total Passing Symmetric Test:  669
- 67   Processing Time:               0.012
- 68 Entered RobustMatcher::computeHomography(keypoints1/2, matches...)...
- 69  -Running RANSAC Constraints/Homography Matrix...
- 70   RobustMatcher::HmgTolerance:  1
- 71   Number Initial Matches:       669
- 72   Total 1st Inliers Remaining:  273
- 73   Total 2nd Inliers Remaining:  266
- 74   Processing Time:              0.041
- 75 Entered EpiPolar RobustMatcher::ransacTest(matches, keypoints1/2...)...
- 76  -Running EpiPolar Constraints/Fundamental Matrix...
- 77   RobustMatcher::EpiTolerance:    1
- 78   RobustMatcher::EpiConfidence:   0.99
- 79   Number Initial Matches:         266
- 80   Inliers on 1st Epipolar:        219
- 81   Inliers on 2nd Epipolar:        209
- 82   Total Passing Epipolar:         209
- 83   Processing Time:                0.01
- 84 Entered RobustMatcher::computeHomography(keypoints1/2, matches...)...
- 85  -Running RANSAC Constraints/Homography Matrix...
- 86   RobustMatcher::HmgTolerance:  1
- 87   Number Initial Matches:       209
- 88   Total 1st Inliers Remaining:  197
- 89   Total 2nd Inliers Remaining:  197
- 90   Processing Time:              0.001
- 91 %% match-pair complete in 5.589 seconds!
-    
+
+

Customizing Feature Matching Algorithms

+

+ There are four options to select and customize Feature Matching + algorithms and their parameters. The two findfeatures + parameters that specify Feature Matching algorithms + are the ALGORITHM and ALGOSPECFILE parameters. + The ALGORITHM parameter accepts a string that adheres to the + specifications as described above that select the detector, + extractor and matcher algorithms. Parameters for each of + those algorithms can be provided in the string as well. + The ALGOSPECFILE parameter accepts the name of a file + containing one or more Feature Matching algorithm configurations + that also adhere to the specification. +

+

+ The ALGORITHM and ALGOSPECFILE parameters should contain the full + specification of the Feature Matching algorithms, including the + optional "/parameters" of the algorithm. At times, its + useful to alter this component of the algorithm to customize for + each set of data without having to maintain seperate files (although + maintaining specs in those file is recommended practice). The + findfeatures ALGORITHM "/parameters" component of the feature + matching specification command line overrides the contents of + the file specified in the PARAMETERS program parameter. This allows + runtime specification of mainly the ALGOSPECFILE PVL-type file + contents to alter behavior of this component of the feature matching + algorithm. +

+

+ Initial values of outlier matching parameters that can be specified in + the PARAMETERS program option can be found in the + Robust Matcher Parameters table. + Use of PARAMETERS excludes the need for these + values to be specified in the command line without having to explicitly + add them to the ALGORITHM string specification or ALGOSPECFILE. +

+

+ The findfeatures GLOBALS program option allows users to specify + "/parameters" keywords that are applied in the Feature Matching + algorithms (as well as FASTGEOM algorithms). Individual parameters + are specifed in accordance with the ALGORITHM string specification + described above. For example, users can select to have all matched + files saved as PNGs by specifying "GLOBALS=SaveRenderedImages:true". + This allows the most convenient method to alter or fully specify + feature matching parameters at runtime without having to edit or + provide any parameterization files. +

+

+ The order of precedence of Feature Matching parameterization is + (lowest to highest) PARAMETERS, ALGOSPECFILE, ALGORITHM and finally + GLOBALS. +

+

The FASTGEOM Algorithm

+

+ The FASTGEOM option provides advanced geometric processing of + training images (FROM, FROMLIST) before they are matched to the query + (MATCH) image. The application of the FASTGEOM option is + indicated when a wide variety of observation geometry is present in + the FROM/FROMLIST images. Once applied, all OpenCV feature matching + algorithms can be used particularly those that are not + + rotation and scale invariant. + This is intended to provide a wide variety of feature matching options + to users that result in better, and more comprehensive image control + networks that contain more images with higher numbers of control points + and higher density counts of control measures. +

+

+ ISIS inherently provides all the necessary cartographic capabilities + that make it possible to preprocess the images to eliminate, as much + as possible, scale and rotation variances in images. By using a priori + geometry provided by SPICE data, findfeatures constructs + transformation matrices for each trainer image that matches the + geometric properties of the query image. Applying the transformation + matrix to each trainer image results in a (fast) projection, or warp, + of the image into the image space of the query image, thus minimizing + scale and rotation invariance. This defines the FASTGEOM processing + objectives in findfeatures. Note that translation, + or spatial offsets, may likely exist between images due to the inherent + nature of a priori ephemeris data. This option is known to have problems + with image sets of irregular bodies, especially for images acquired along + the long axis of the target body.. +

+

+ The FASTGEOM algorithms requires all input images to have + ISIS-based SPICE ephemeris applied by the spiceinit application. + If any image does not have SPICE or no common latitude/longitude + coordinates can be determined between the trainer image and the + query image, they are exclude from feature matching and will be + recorded in the TONOGEOM file. Feature matching will continue + if one or more trainer image is successfully transformed. +

+

Effective Use of FASTGEOM Algorithms

+

+ The FASTGEOM option provides two different algorithms - grid + and radial - that can be used to associate common latitude/longitude + coordinates between two images. These common latitude/longitude + coordinates are translated to line/sample image coordinates in both + images which are then used to create a + + homography + 3x3 transformation matrix mapping the trainer line/sample coordinate + pairs into the cooresponding query line/sample image coordinates. After + the trainer images are read in, the homography transformation matrix is + applied using a + perspective warp projection + of the trainer image. The GEOMTYPE parameter determines the + type of output image is produced from the projection. This produces + the projected trainer image that will be matched to the query image. + The query image is not modified in this process. +

+

+ Several common parameters govern behavior of both the grid and + radial FASTGEOM algorithms. Note each algorithm is applied + independently to every query/trainer image pair. For each image pair, + the values used/computed and the resulting homography matrix are + logged in the DEBUGLOG file (when DEBUG=true). The algorithm + parameters are described in the following table. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ FASTGEOM Common Parameters +

+
KeywordDefaultDescription
FastGeomAlgorithmRadial + Specifies the name of the FASTGEOM point mapping algorithm + to use to compute common latitude/longitude and line/sample + coordinates in the query and trainer images. Valid options are + Radial and Grid. +
FastGeomPoints25 + The minimum number of valid mapping points required in order to + compute the homography image transformation matrix. If after + the algorithm completes point mapping computations there are + no FastGeomPoints valid points, the image is not added + to the trainer match list and reported in TONOGEOM. The + minimum value is 25, the maximum is all pixels in the images. +
FastGeomTolerance3.0 + The maximum pixel outlier tolerance allowed in computing the + homography matrix from query and trainer image mapped points + generated from the FASTGEOM algorithms. The outlier for + each mapping point is computed by using the homography matrix + to translate each trainer point to the perspective query point + and calculating the Euclidean distance from the actual query + point. If the absolute distance is larger than FastGeomTolerance + the point is rejected. Note the remaining inlier points are + allowed to be less than FastGeomPoints. +
FastGeomQuerySampleTolerance0.0 + This parameter allows the number of query image samples to exceed + the actual number of pixels in the image FOV by this tolerance on + both left and right boundaries of the image. This is intended to give + the algorithms the best chance to collect the necessary number of + mapping points to compute the homography tramsformation matrix. + This value is not recommended to be too large, perhaps 5 - 10 + pixels at the most. +
FastGeomQueryLineTolerance0.0 + This parameter allows the number of query image lines to exceed + the actual number of pixels in the image FOV by this tolerance on + both top and bottom boundaries of the image. This is intended to give + the algorithms the best chance to collect the necessary number of + mapping points to compute the homography transformation matrix. + This value is not recommended to be too large, perhaps 5 - 10 + pixels at the most. +
FastGeomTrainSampleTolerance0.0 + This parameter allows the number of trainer image samples to exceed + the actual number of pixels in the image FOV by this tolerance on + both left and right boundaries of the image. This is intended to give + the algorithms the best chance to collect the necessary number of + mapping points to compute the homography tramsformation matrix. + This value is not recommended to be too large, perhaps 5 - 10 + pixels at the most. +
FastGeomTrainLineTolerance0.0 + This parameter allows the number of trainer image lines to exceed + the actual number of pixels in the image FOV by this tolerance on + both top and bottom boundaries of the image. This is intended to give + the algorithms the best chance to collect the necessary number of + mapping points to compute the homography tramsformation matrix. + This value is not recommended to be too large, perhaps 5 - 10 + pixels at the most. +
FastGeomDumpMappingfalse + This parameter informs the FASTGEOM algorithm to dump the + mapping points of every query/trainer image pair to a CSV file. + This will produce a file in the current directory of the form + queryfile_trainerfile_{FastGeomAlgorithm}.fastgeom.csv + where queryfile is the base name of the MATCH file with no + directory or file extension, the trainerfile is the base + name of the FROM/FROMLIST file with no directory or file extension, + and {FastGeomAlgorithm} is the type of algorithm specified + in that parameter. The columns written to the file are: + QuerySample, QueryLine, TrainSample, TrainLine, + Latitiude, Longitude, Radius, X, Y, Z, InTrainFOV. + All values are floating point except InTrainFOV which is either + True or False, True indicating the point is (valid) in both + images. The name of this file is indicated in th + DEBUGLOG file as PointDumpFile for eac + query/trainer image pair. +
+
+

FASTGEOM Grid Algorithm

+

+ The FASTGEOM Grid algorithm computes a coreg-like + rectangular set of grid points that are evenly space in each image axis. + This algorithm will continue to refine the grid by decreasing the spacing + between each iteration until at least FastGeomPoints valid mapping + points are found. The algorithm continues to refine the spacing until + every pixel in the query image is check for a valid mapping coordinate + into the trainer image. Therein lies the potential for this algorithm to + consume massive time and compute resources for images that contain very + few or no valid common geometric points between query and trainer images. + Therefore, some parameters are provided that place + constraints/boundaries on the variables of this algorithm. These + parameters are described in the following table. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ FASTGEOM Grid Parameters +

+
KeywordDefaultDescription
FastGeomGridStartIteration0 + Specifies the starting iteration of the grid loop that calculates + mapping points from query to trainer images. The first loop is 0 + and it is computed so that it will be as close to the number of + points specified in FastGeomPoints. It can start with any + itertion less than or equal to FastGeomGridStopIteration. +
FastGeomGridStopIterationcalculated + Specifies the terminating iteration of the grid loop that calculates + mapping points from query to trainer images. The last loop, if not + specified by the user, is calculated as + max( max(query lines/samples), max(trainer lines/samples) ) / 2.0. + It is possible to specify no points if FastGeomGridStartIteration + is greater than FastGeomGridStopIteration. +
FastGeomGridIterationStep1 + Specifies the iteration increment that is added to the current + iteration that is used to calculate the line/sample grid spaceing. + The actual grid spacing for line and sample axes are computed + as max( 1.0, queryAxisSize/(currinc*1.0)), where + queryAxisSize is the number of samples or lines in the axis and + currinc is increment + ( iteration * 2 ), where + iteration is the current increment + FastGeomGridIterationStep. +
FastGeomGridSaveAllPointsfalse + At each new iteration in the grid algorithm, all points in the + previous iteration are deleted. This true/false flag can be set + to true to preserve all mapping points in earlier iterations. This + may help in reaching the minimum FastGeomPoints but also + runs the risk of duplicate points, thus biasing the homography + matrix or creating an invalid matrix. This option is not recommended + as it may create more problems than it resolves. +
+

+ This algorithm works best for high resolution images where there + are little or no discontinuities in geometry and no limbs. It also + can result in excessively long run times and/or significant computed + resources for approach images where valid geometry is only in a small + localized region in the image FOV. In those cases, the FASTGEOM radial + algorithm is recommended. +

+
+

FASTGEOM Radial Algorithm

+

+ The FASTGEOM Radial algorithm computes common geometric mapping + points in the query and trainer images that are generated from a radial + pattern originated at the center of the query image. The radial algorithm + differs most from the grid algorithm because it is not iterative. It is + a one shot algorithm where a single pattern is generated from parameters + that are designed to provide a dense radial pattern. It can be much more + efficient than the grid algorithm but runs a higher risk of failure due to + underdetermination of sufficient number of common geometric points. This + is the default algorithm if one is not specified by the user. + The parameters that can be provided to customize the radial pattern + created in this algorithm are described in the following table. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ FASTGEOM Radial Parameters +

+
KeywordDefaultDescription
FastGeomRadialSegmentLength25 + Specifies the length in pixels between each radial set of mapping + points on the query image. The center pixel always has a point. + Each subseqent circle of points has a radius distance of + FastGeomRadialSegmentLength pixels from the previous + circular pattern of points. The number of ring segments is computed + as sqrt( (nlines^2) + (nsamples^2) ) / FastGeomRadialSegmentLength. +
FastGeomRadialPointCount5.0 + Number of points on the first circle. This parameter specifies the + density of points on the first circle from the center point. Each + subseqent circle will have a multiple of points on a 360 degree + circle spaced evenly by the number of points computed for each + circle/ring. +
FastGeomRadialPointFactor1 + This is the point factor applied to increase the density of points + that are spaced on the 360 degree circle at that segment. The + number of points is a fuction of the ring segment from the center + multiplied by the product of the FastGeomRadialPointCount + and the FastGeomRadialPointFactor. The equation used to + compute the number of points on the ring segment is + FastGeomRadialPointCount + ( (FastGeomRadialPointCount * + FastGeomRadialPointFactor) * (ring -1)). +
FastGeomRadialSegmentsoptional + This parameter is optional and will supercede + FastGeomRadialSegmentLength. Sometimes its just easier + to directly specify the number of circular ring segments rather + than pixel distance between each ring segment. By providing a + value greater than 0 in this parameter (e.g., using + GLOBALS), this value will directly specify the number + of rings segments in the image rather than the number of rings + computed from FastGeomRadialSegmentLength, the distance + between ring segments. +
+

+ This algorithm produces a set of rings with increasing point density + along each ring. It may perform better than the grid algorithm since + it makes a single pattern in the image. This pattern is used to compute + common mapping points to compute the homography matrix for determining + the prospective matrix to project each trainer image independently. +

+

Customizing the FASTGEOM Algorithms

- In the above example, lines 2-13 provide general information about the - program and compute environment. If MAXTHREADS were set to a value less - than 4, number of total threads (line 11) would reflect this number. - Line 15 specifies the precise time the matcher algorithm was invoked. - Line 18-25 shows the algorithm string specification, names of query - (MATCH) and train (FROM) images and the full and rendered sizes of - images. Lines 27 and 28 show the total number of keypoints or features - that were detected by the SURF detector for both the query (11823) and - train (11989) images. Lines 31-33 indicate the descriptors of all the - feature keypoints are being extracted. Extraction of keypoint - descriptors can be costly under some conditions. Users can restrict the - number of features detected by using the MAXPOINTS parameter specify the - maximum numnber of points to save. The values in brackets in lines 27 - and 28 will show the total amount of features detected if MAXPOINTS - are used. + The default parameters for FASTGEOM "grid" and "radial" algorithms + are used if FASTGEOM=true when findfeatures is run on a set of + images. The default values for FASTGEOM are described in the + table above and available in + $ISISROOT/appdata/templates/findfeatures/findfeatures_fastgeom_defaults.pvl. + This file can be copied and edited as needed for project wide application + of FASTGEOM parameters. This file is intended to be provided in the + findfeatures PARAMETERS program option. It can coexist along with + Feature Matching PVL parameters. Note this combination of parameters + neatly centralizes all algorithms and their parameters in one file + specifed at runtime to achieve desired behavior in all findfeatures + algorithms. Each set of parameters for a particular + algorithm can be placed in their own specific PVL Object or Group section + with arbitrary names in this file.

- Outlier detection begins at line 35. The Ratio test is performed first. + However, as is with the Feature Matching parameterization, the + findfeatures GLOBALS program option can also be used to specify + or alter each FASTGEOM algorithm in the same way it is used to + change Feature Matching behavior. Any FASTGEOM parameter in both + the Grid and Radial algorithms can be specifed according to the + ALGORITHM string specification described above. Perhaps the most + useful aspect of this program option is to directly specify the + FASTGEOM option to use at runtime. For example, the "grid" algorithm + can be selected at runtime as "GLOBALS=FastGeomAlgorithm:grid" thus + overriding the default "radial" algorithm. Note all algorithm keywords + specified in the Grid and Radial parameter tables can be provided in + the GLOBALS parameter separated by the @ symbol. +

+

+ Note, as is with the Feature Matching algorithms, any "keyword:value" + pair specified in the GLOBALS option takes highest precidence and + overrides values specifed by other means, such as contained in the + PVL file provided in PARAMETERS or algorithm defaults. +

+

Using Debugging to Diagnose Behavior

+

+ An additional feature of findfeatures is a detailed debugging + report of processing behavior in real time for all matching and + outlier detection algorithms. The data produced by this option is + very useful to identify the exact processing step where some matching + operations may result in failed matching operations. In turn, this + will allow users to alter parameters to address these issues that + can lead to better matches that would otherwise not be achieved. +

+

+ To invoke this option, users set DEBUG=TRUE and provide an optional + output file (DEBUGLOG=filename) where the debug data is written. If + no file is specified, output defaults to the terminal device. Below is + an example (see the example section for details) of a debug session + with line numbers added for reference of the description that + follows. The findfeatures command used to generate this example is: +

+
+
+findfeatures algorithm='fastx@threshold:25@type:2/brief/parameters@maxpoints:500' \
+             match=EW0211981114G.cub \
+             from=EW0242463603G.cub \
+             fastgeom=true \
+             geomtype=camera \
+             geomsource=both \
+             fastgeompoints=25 \
+             epitolerance=3.0 \
+             ratio=0.99 \
+             hmgtolerance=3.0 \
+             globals='FastGeomDumpMapping:true' \
+             networkid="EW0211981114G_EW0242463603G" \
+             pointid='EW211981116G_????' \
+             onet=EW0211981114G.net \
+             tolist=EW0211981114G_cubes.lis \
+             tonogeom=EW0211981114G_nogeom.lis \
+             tonotmatched=EW0211981114G_notmatched.lis \
+             description='Create image-image control network' \
+             debug=true \
+             debuglog=EW0211981114G.debug.log
+
+
+

+ Note the file TONOGEOM is not created since there were no failures + in FASTGEOM processing. The TONOTMATCHED file is also not created + because all files in FROM/FROMLIST were successfully included in + the output control network. +

+
+  1:  ---------------------------------------------------
+  2:  Program:        findfeatures
+  3:  Version         1.2
+  4:  Revision:       2023-06-09
+  5:  RunTime:        2023-06-16T15:27:19
+  6:  OpenCV_Version: 4.5.5
+  7:
+  8:  System Environment...
+  9:  Number available CPUs:     8
+ 10:  Number default threads:    8
+ 11:  Total threads:             8
+ 12:
+ 13:  Image load started at  2023-06-16T15:27:19
+ 14:
+ 15:  ++++ Running FastGeom ++++
+ 16:  *** QueryImage: EW0211981114G.cub
+ 17:  *** TrainImage: EW0242463603G.cub
+ 18:    FastGeomAlgorithm:            radial
+ 19:    FastGeomPoints:               25
+ 20:    FastGeomTolerance:            3
+ 21:    FastGeomQuerySampleTolerance: 0
+ 22:    FastGeomQueryLineTolerance:   0
+ 23:    FastGeomTrainSampleTolerance: 0
+ 24:    FastGeomTrainLineTolerance:   0
+ 25:
+ 26:  --> Using Radial Algorithm train-to-query mapping <--
+ 27:    FastGeomMaximumRadius:         724.077
+ 28:    FastGeomRadialSegmentLength:   25
+ 29:    FastGeomRadialPointCount:      5
+ 30:    FastGeomRadialPointFactor:     1
+ 31:    FastGeomRadialSegments:        29
+ 32:
+ 33:  ==> Radial Point Mapping complete <==
+ 34:    TotalPoints:     2031
+ 35:    ImagePoints:     1333
+ 36:    MappedPoints:    1333
+ 37:    InTrainMapFOV:   636
+ 38:
+ 39:  --> Dumping radial points <---
+ 40:    PointDumpFile:     EW0211981114G_EW0242463603G.radial.fastgeom.csv
+ 41:    TotalPoints:       1333
+ 42:
+ 43:  ==> Geometric Correspondence Mapping complete <==
+ 44:    TotalPoints:       636
+ 45:
+ 46:  --> Running Homography Image Transform <---
+ 47:    IntialPoints:       636
+ 48:    Tolerance:          3
+ 49:    TotalLmedsInliers:  563
+ 50:    PercentPassing: 88.522
+ 51:
+ 52:    MatrixTransform:
+ 53:      0.645981,-0.0158572,113.771
+ 54:      -0.0350108,0.628872,337.353
+ 55:      -8.52086e-05,2.53351e-06,1
+ 56:
+ 57:  Image load complete at 2023-06-16T15:27:19
+ 58:
+ 59:  Total Algorithms to Run:     1
+ 60:
+ 61:  @@ matcher-pair started on 2023-06-16T15:27:19
+ 62:
+ 63:  +++++++++++++++++++++++++++++
+ 64:  Entered RobustMatcher::match(MatchImage &query, MatchImage &trainer)...
+ 65:    Specification:   fastx@threshold:25@type:2/brief/parameters@maxpoints:500/BFMatcher@NormType:NORM_HAMMING@CrossCheck:false
+ 66:  **  Query Image:   EW0211981114G.cub
+ 67:         FullSize:     (1024, 1024)
+ 68:         Rendered:     (1024, 1024)
+ 69:  **  Train Image:   EW0242463603G.cub
+ 70:         FullSize:     (1024, 1024)
+ 71:         Rendered:     (1024, 1024)
+ 72:  --> Feature detection...
+ 73:    Keypoints restricted by user to 500 points...
+ 74:    Total Query keypoints:    512 [14121]
+ 75:    Total Trainer keypoints:  518 [9511]
+ 76:    Processing Time:          0.005
+ 77:    Processing Keypoints/Sec: 4.7264e+06
+ 78:  --> Extracting descriptors...
+ 79:    Processing Time(s):         0.005
+ 80:    Processing Descriptors/Sec: 4.7264e+06
+ 81:
+ 82:  *Removing outliers from image pairs
+ 83:  Entered RobustMatcher::removeOutliers(Mat &query, vector<Mat> &trainer)...
+ 84:  --> Matching 2 nearest neighbors for ratio tests..
+ 85:    Query, Train Descriptors: 452, 502
+ 86:    Computing query->train Matches...
+ 87:    Total Matches Found:   452
+ 88:    Processing Time:       0.001
+ 89:    Matches/second:        452000
+ 90:    Computing train->query Matches...
+ 91:    Total Matches Found:   502
+ 92:    Processing Time:       0.001 <seconds>
+ 93:    Matches/second:        502000
+ 94:   -Ratio test on query->train matches...
+ 95:  Entered RobustMatcher::ratioTest(matches[2]) for 2 NearestNeighbors (NN)...
+ 96:    RobustMatcher::Ratio:       0.99
+ 97:    Total Input Matches Tested: 452
+ 98:    Total Passing Ratio Tests:  421
+ 99:    Total Matches Removed:      31
+100:    Total Failing NN Test:      31
+101:    Processing Time:            0
+102:   -Ratio test on train->query matches...
+103:  Entered RobustMatcher::ratioTest(matches[2]) for 2 NearestNeighbors (NN)...
+104:    RobustMatcher::Ratio:       0.99
+105:    Total Input Matches Tested: 502
+106:    Total Passing Ratio Tests:  469
+107:    Total Matches Removed:      33
+108:    Total Failing NN Test:      33
+109:    Processing Time:            0
+110:  Entered RobustMatcher::symmetryTest(matches1,matches2,symMatches)...
+111:   -Running Symmetric Match tests...
+112:    Total Input Matches1x2 Tested: 421 x 469
+113:    Total Passing Symmetric Test:  194
+114:    Processing Time:               0
+115:  Entered RobustMatcher::computeHomography(keypoints1/2, matches...)...
+116:   -Running RANSAC Constraints/Homography Matrix...
+117:    RobustMatcher::HmgTolerance:  3
+118:    Number Initial Matches:       194
+119:    Total 1st Inliers Remaining:  149
+120:    Total 2nd Inliers Remaining:  149
+121:    Processing Time:              0
+122:  Entered EpiPolar RobustMatcher::ransacTest(matches, keypoints1/2...)...
+123:   -Running EpiPolar Constraints/Fundamental Matrix...
+124:    RobustMatcher::EpiTolerance:    3
+125:    RobustMatcher::EpiConfidence:   0.99
+126:    Number Initial Matches:         149
+127:    Inliers on 1st Epipolar:        149
+128:    Inliers on 2nd Epipolar:        146
+129:    Total Passing Epipolar:         146
+130:    Processing Time:                0.005
+131:  Entered RobustMatcher::computeHomography(keypoints1/2, matches...)...
+132:   -Running RANSAC Constraints/Homography Matrix...
+133:    RobustMatcher::HmgTolerance:  3
+134:    Number Initial Matches:       146
+135:    Total 1st Inliers Remaining:  145
+136:    Total 2nd Inliers Remaining:  145
+137:    Processing Time:              0.001
+138:  %% match-pair complete in 0.019 seconds!
+139:
+140:  Entering MatchMaker::network(cnet, solution, pointmaker)...
+141:    Images Matched:                 1
+142:    ControlPoints created:          145
+143:    ControlMeasures created:        290
+144:    InvalidIgnoredPoints:           0
+145:    InvalidIgnoredMeasures:         0
+146:    PreserveIgnoredControl          No
+147:
+148:    -- Valid Point/Measure Statistics --
+149:    ValidPoints            145
+150:    MinimumMeasures:       2
+151:    MaximumMeasures:       2
+152:    AverageMeasures:       2
+153:    StdDevMeasures:        0
+154:    TotalMeasures:         290
+155:
+156:  Session complete in 00:00:00.277 of elapsed time
+
+

+ In the above example, lines 2-11 provide general information about the + program and compute environment. If MAXTHREADS were set to a value less + than 8, the number of total threads (line 11) would reflect this number. + Lines 13 indicates the time when image loading was initiated. +

+

+ Lines 15-55 + reports the results of FASTGEOM processing for all input images. In this + case, there is only one image processed. Lines 16-55 would repeat for + every image pair that is processed by the FASTGEOM algorithm. Lines 18-24 + indicate the values determined the common algorithm parameters as shown in + the FASTGEOM Common Parameters table. + Lines 26-31 indicate the parameters determined/used for the radial mapping + algorithm (the default) as described in the + FASTGEOM Radial Parameters table. + Lines 33-37 report the mapping results between the query and train images. + The TotalPoints are larger than the ImagePoints due to the nature of the + radial algorithm which includes the points outside the boundaries of the + FOV of the query image in the outer rings of the radial pattern generated. + Lines 39-41 indicate the dump file for the mapped points. It exists only + because we added "globals=FastGeomDumpMapping:true" to the command line. + This file is generated from the base names of the input files. Line 44 + indicates the number of valid points in both images used to construct + the homography transformation matrix applied by a perspective warp + algorithm on the train image. Lines 46-50 report the results of the + generation of the homography matrix. And finally, lines 52-55 show the + actual homography matrix produced for this image pair. After all images + are processed by the FASTGEOM algorithm, line 57 reports the processing + time the algorithm completed. +

+

+ Line 61 specifies the precise time the matcher algorithm was invoked. + Line 64-71 shows the algorithm string specification, names of query + (MATCH) and train (FROM/FROLIST) images and the full and rendered sizes + of images. Lines 74 and 75 show the total number of keypoints or + features that were returned [detected] by the FASTX detector for both + the query (512 [14121]) and train (518 [9511]) images. Lines 78-80 + indicate the descriptors of all the feature keypoints are being + extracted. Extraction of keypoint descriptors can be costly under some + conditions. Users can restrict the number of features detected by using + the MAXPOINTS parameter, which was provided in the parameters of the + ALGORITHM specification for this run. The values in brackets in + lines 74 and 75 will show (and differ from) the total amount of + features detected if MAXPOINTS is provided. +

+

+ Outlier detection begins at line 82. The Ratio test is performed first. Here the matcher algorithm is invoked for each match pair, regardless - of the number of train (FROMLIST) images provided. For each keypoint in + of the number of train (FROMLIST) images provided. For each keypoint in the query image, the two nearest matches in the train image are - computed and the results are reported in lines 39-42. Then the - bi-directional matches are computed in lines 43-46. A bi-directional - ratio test is computed for the query->train matches in lines 47-54 and - then train->query in lines 55-62. You can see here that a significant + computed and the results are reported in lines 86-89. Then the + bi-directional matches are computed in lines 90-93. A bi-directional + ratio test is computed for the query->train matches in lines 94-101 and + then train->query in lines 102-109. You can see here that a significant number of matches are removed in this step. Users can adjust this behavior, retaining more points by setting the RATIO parameter closer to 1.0. The symmetry test, ensuring matches from query->train have the same - match as train->query, is reported in lines 63-67. In lines 68-74, the - homography matrix is computed and outliers are removed where the - tolerance exceeds HMGTOLERANCE. Lines 75-83 shows the results of the + match as train->query, is reported in lines 110-114. In lines 115-121, + the homography matrix is computed and outliers are removed where the + tolerance exceeds HMGTOLERANCE. Lines 122-130 shows the results of the epipolar fundamental matrix computation and outlier detection. Matching - is completed in lines 84-90 which report the final spatial homography + is completed in lines 131-137 which report the final spatial homography computations to produce the final transformation matrix between the - query and train images. Line 89 shows the final number of control - measures computed between the image pairs. Lines 35-90 are repeated for - each query/train image pair (with perhaps slight formatting - differences). Line 91 shows the total processing time for the matching - process. + query and train images. Line 136 shows the final number of control + measures computed between the image pairs. Lines 82-137 are repeated + for each query/train image pair (with perhaps slight formatting + differences). Line 138 shows the total processing time for the + matching process. +

+

+ Lines 140-146 report the generation of the control network. This process + connects all the same features in the each of the images (control measures) + into individual sets of control points. Users can also choose to preserve + all ignored control points by adding "PreserveIgnoredControl:true" in + the GLOBALS parameter. The value used specified is reported on line 146. + If true, this will result in some control points being marked as ignored + in the output control point. These kinds of points will typically be + created when GEOMSOURCE=both and all control measures within a control + point fails valid geometry tests when the output network is created.

Evaluation of Matcher Algorithm Performance

-

- findfeatures provides users with many features and options to - create unique algorithms that are suitable for many of the diverse - image matching conditions that naturally occur during a spacecraft - mission. Some are more suited for certain conditions that others. But - how does one determine which algorithm combination performs the best - for an image pair? By computing standard performance metrics, one can - make a determination as to which algorithm performs best. -

- Using the ALGOSPECFILE parameter, users can specify one or more - algorithms to apply to a given image matching process. Each algorithm - specified, one per line in the input file, results in a the creation of - a unique robust matcher algorithm thatis applied to the input files in - succession. The performance of each algorithm is computed for each of - the matcher from a standard set of metrics described in a thesis titled - - Efficient matching of robust features for embedded SLAM. From the - metrics described in this paper, a single metric that measures the - abilities of the whole matching process is computed that are relevant to - all three FBM steps: detection, description and matching. This - metric is called Efficiency. The Efficiency metric is - computed from two other metrics called Repeatability and - Recall. + findfeatures provides users with many features and options to + create unique algorithms that are suitable for many of the diverse + image matching conditions that naturally occur during a spacecraft + mission. Some are more suited for certain conditions than others. But + how does one determine which algorithm combination performs the best + for an image pair? By computing standard performance metrics, one can + make a determination as to which algorithm performs best. +

+

+ Using the ALGOSPECFILE parameter, users can specify one or more + algorithms to apply to a given image matching process. Each algorithm + specified, one per line in the input file, results in the creation of + a unique robust matcher algorithm that is applied to the input files in + succession. The performance of each algorithm is computed for each of + the matcher from a standard set of metrics described in a thesis titled + + Efficient matching of robust features for embedded SLAM. From the + metrics described in this paper, a single metric that measures the + abilities of the whole matching process is computed that are relevant + to all three FBM steps: detection, description and matching. This + metric is called Efficiency. The Efficiency metric is + computed from two other metrics called Repeatability and + Recall.

- Repeatability represents the ability to detect the same point - in the scene under viewpoint and lighting changes and subject to - noise. The value of Repeatability is calculated as: + Repeatability represents the ability to detect the same point + in the scene under viewpoint and lighting changes and subject to + noise. The value of Repeatability is calculated as: +

+
- Repeatability = |correspondences| / |query keypoints| + Repeatability = |correspondences| / |query keypoints| +

+
Here, correspondences are the total number of matches that were made after all FBM processing including outlier detection. Repeatability is only relevant to the feature detector, and nothing about feature @@ -1078,24 +1704,32 @@ Repeatability, the better performance of feature detector.

- Recall represents the ability to find the correct - matches based on the description of detected features, The value of - Recall is calculated as: + Recall represents the ability to find the correct + matches based on the description of detected features, The value of + Recall is calculated as: +

+
- Recall = |correct matches| / |correspondences| + Recall = |correct matches| / |correspondences| +

+
Because the detected features are already determined, Recall only shows the performance of the feature descriptor and descriptor matcher. The higher value of Recall, the better performance of descriptor and matcher.

- Efficiency combines the Repeatability and - Recall. It is defined as: - - Efficiency = Repeatability * Recall = |correct matches| / |query - keypoints| + Efficiency combines the Repeatability and + Recall. It is defined as: +

+
+ + Efficiency = Repeatability * Recall = |correct matches| / |query + keypoints| +

+
Efficiency measures the ability of the whole image matching process, it is relevant to all three steps: detection, description and matching. The higher value of Efficiency , the more accurate @@ -1110,10 +1744,11 @@ MatchSolution group. Here is an example:

 Group = MatchSolution
-    Matcher      = surf@hessianThreshold:100/surf/BFMatcher@NormType:NORM_L2@CrossCheck:false
-    MatchedPairs = 1
-    Efficiency   = 0.040744395883265
-  End_Group
+  Matcher      = orb@nfeatures:3000/sift/BFMatcher@NormType:NORM_L2@CrossCheck:false
+  MatchedPairs = 1
+  ValidPairs   = 1
+  Efficiency   = 0.019733333333333
+End_Group
       

@@ -1177,6 +1812,33 @@ Group = MatchSolution method to pass in clones of query and trainers. This avoids pointer issues which were mixing up data and causing failures. Fixes #3341. + + Fix matrix inversion error on empty matrix. (Fixes #4639) + Identify images that fail FastGeom transform and exclude from matching. + Add TONOGEOM parameter to write failed file loads or FastGeom error + file list if the transform cannot be determined. Improve FastGeom + transform algorithm using new radial point mapping scheme. Add more + debugging output to help diagnose problem images/procedures. + + + Reorganized FastGeom (added methods radial_algorithm(), grid_algorithm() + and dump_point_mapping()) and cleaned up code after USGS/Astro code + review. Added new parameter GLOBALS that provides convenience for setting + or resetting algorithm or processing variables. (Fixes #4772) + + + Prevent the creation of the TONOTMATCHED file if all images are + successfully paired with another image. Significantly updated and + improved all documentation. Thoroughly explain the FASTGEOM option. + Describe how to apply and parameterize the new radial and improved grid + FASTGEOM mapping algorithms. Add two new examples to show how to + use findfeatures. (References #4772) + + + Fix bug when using projected images and mosaics. Instantiation and use + of projection class was fixed to correctly return geometry data for + these images. (References #4772) + @@ -1188,7 +1850,7 @@ Group = MatchSolution Input Image to be Translated - This cube/image (train) will be translated to register to the MATCH + This cube/image (train) will be translated to register to the MATCH (query) cube/image. This application supports other common image formats such as PNG, TIFF or JPEG. Essentially any image that can be read by OpenCV's @@ -1214,7 +1876,7 @@ Group = MatchSolution cube filenames. The cubes identified inside this file will be used to create the control network. All input images are converted to 8-bit when they are read. The following is an example of the - contents of a typical FROMLIST file: + contents of a typical FROMLIST file:

             AS15-M-0582_16b.cub
@@ -1225,7 +1887,7 @@ Group = MatchSolution
             AS15-M-0587_16b.cub
           

- Each file name in a FROMLIST file should be on a separate line. + Each file name in a FROMLIST file should be on a separate line.

None @@ -1258,10 +1920,10 @@ Group = MatchSolution Output ControlNet network file of matched features This file will contain the Control Point network results of - findfeatures in a binary format. There will be no false or + findfeatures in a binary format. There will be no false or failed matches in the output control network file. Using this control network and TOLIST in the qnet application, the results - of findfeatures can be visually assessed. + of findfeatures can be visually assessed. None @@ -1276,10 +1938,10 @@ Group = MatchSolution This file will contain the list of (cube) files in the control network. For multi-image matching, some files may not have matches - detected. These files will not be written to TOLIST. The MATCH file + detected. These files will not be written to TOLIST. The MATCH file is always added first and all other images that have matches are added to TOLIST. Using this list and the ONET in the qnet - application, the results of findfeatures can be visually assessed. + application, the results of findfeatures can be visually assessed. None *.lis @@ -1306,6 +1968,38 @@ Group = MatchSolution None *.lis + + + filename + output + + Output list of FROM/FROMLIST cube files that failed FastGeom + + +

+ This file will contain the list of (cube) files that could not + find valid geometry mapping during the FastGeom process. Note + that this process uses the current state of geometry for each + image, which is likely to be a prior SPICE. This could be the + cause of failures rather than no common ground coverage between + the MATCH and FROM/FROMLIST images. + +

+

+ NOTE this option supports the scenario where users cannot + verify the FROM/FROMLIST images have any common coverage with + the MATCH image. Large lists of images will have significantly + compute overhead, so use sparingly. +

+

+ These images are excluded from the matching process since a valid + fast geom transform cannot be computed and are written to a + file name as specfied in this parameter. +

+
+ None + *.lis +
@@ -1331,7 +2025,7 @@ Group = MatchSolution To accomodate a potentially large set of feature algorithms, you can provide them in a file. This format is the same as the - ALGORITHM format, but each unique algorithm must be specifed + ALGORITHM format, but each unique algorithm must be specifed on a seperate line. Thoeretically, the number you specify is unlimited. This option is particularly useful to generate a series of algorithms that vary parameters for any of the @@ -1348,9 +2042,10 @@ Group = MatchSolution If true, information about the detector, extractor, matcher, and - parameters specified in the ALGORITHM or ALGOSPECFILE parameters - will be output. If multiple sets of algorithms are specified, - then the details for each set will be output. + parameters specified in the ALGORITHM or + ALGOSPECFILE parameters will be output. If multiple sets + of algorithms are specified, then the details for each set will + be output. No @@ -1505,7 +2200,7 @@ End boolean Print debugging statements of the matcher algorithm - At times, things go wrong. By setting DEBUG=TRUE, information is + At times, things go wrong. By setting DEBUG=TRUE, information is printed as elements of the matching algorithm are executed. This option is very helpful to monitor the entire matching and outlier detection processing to determine where adjustments in the @@ -1516,7 +2211,7 @@ End filename - File to write (append) debugging information to + File to write (append) debugging information to Provide a file that will have all the debugging content appended as it is generated in the processing steps. This file can be @@ -1535,24 +2230,51 @@ End File containing special algorithm parameters This file can contain specialized parameters that will modify - certain behaviors in the robust matcher algorithm. They can - vary over time and are documented in the application - descriptions. + certain behaviors in the robust matcher and FASTGEOM + algorithms. They can vary over time and are documented in the + application descriptions. None *.conf + + string + String containing global parameters and values + +

+ This string can contain additional parameters that will + set or reset global parameters provded by other mechanisms. + This program option is primarily useful for making small + scale adjustments to algorithm parameters in a convenient + and efficient manner. +

+

+ For example, using this parameter is the most straightforward + way to select the FASTGEOM algorithm. To select the FASTGEOM + Grid algorithm and constrain the number of interations to 10, + use GLOBALS=FastGeomAlgorithm:Grid@FastGeomGridStopIteration:10". +

+

+ There is very little robust error detection or any confirmation + that the parameter names are valid. Any misspelled parameters + are not detected and ill-formed parameter strings may not + result in errors, but passthrough and mangle other parameter + construction. +

+
+ None +
integer - Maximum number of keypoints to detect + Maximum number of keypoints to detect Specifies the maximum number of keypoints to save in the detection phase. If a value is not provided for this parameter, there will be no restriction set on the number of keypoints that will be used to match. If specified, then - approximately MAXPOINTS keypoints with the highest/best detector + approximately MAXPOINTS keypoints with the highest/best detector response values are retained and passed on to the extractor and matcher algorithms. This parameter is useful for detectors that produce a high number of features. A large number of features @@ -1600,7 +2322,7 @@ End feature may deviate from the Epipolar lines for each matching feature. - 3.0 + 3.0 @@ -1613,7 +2335,7 @@ End determination ratio. A value of 1.0 requires that all pixels be valid in the epipolar computation.
- 0.99 + 0.99 @@ -1636,13 +2358,13 @@ End

The parameter is used as a tolerance in the computation of the distance between keypoints using the homography matrix - relationship between the MATCH image and each FROM/FROMLIST + relationship between the MATCH image and each FROM/FROMLIST image. This will throw points out that are (dist > TOLERANCE * min_dist), the smallest distance between points.

- 3.0 + 3.0 @@ -1653,9 +2375,9 @@ End This parameter allows users to control the number of threads to use for image matching. A default is to use all available threads - on system. If MAXTHREADS is specified, the maximum number of CPUs + on system. If MAXTHREADS is specified, the maximum number of CPUs are used if it exceeds the number of CPUs physically available - on the system or no more than MAXTHREADS will be used. + on the system or no more than MAXTHREADS will be used. 0 @@ -1667,8 +2389,8 @@ End Perform fast geometry image transform When TRUE, this option will perform a fast geometric linear - transformation that projects each FROM/FROMLIST image to - the camera space of the MATCH image. Note this option + transformation that projects each FROM/FROMLIST image to + the camera space of the MATCH image. Note this option theoretically is not needed for scale/rotation invariant feature matchers such as SIFT and SURF but there are limitations as to the invariance of these matchers. For matchers that are not scale @@ -1676,16 +2398,19 @@ End to orient each images to similar spatial consistency. Users should determine the capabilities of the matchers used. - false + false integer - Specify the maximum number of geometry points used to compute the - fast geometry reprojection + Minimium points to compute perspective matrix in FASTGEOM + Specify the minimun number of common geometry mapping points + used to compute the FASTGEOM homography transformation matrix + that is used in the perspective warp of a FROM/FROMLIST image + into the camera space of the MATCH image. 25 @@ -1695,8 +2420,8 @@ End CAMERA Type of fast geom mapping to apply - Provide options as to how FASTGEOM projects data in the FROM (train) - image to the MATCH (query) image space. + Provide options as to how FASTGEOM projects data in the FROM (train) + image to the MATCH (query) image space. @@ -1717,7 +2442,7 @@ End Fastgeom will crop the FROM image to common area of MATCH - Fastgeom will crop the FROM image to common area of MATCH and + Fastgeom will crop the FROM image to common area of MATCH and retain on the approximate size of the cropped region. This option is useful to limit features to be identified in the common region in both images. @@ -1729,11 +2454,11 @@ End Fastgeom will fully map project all of the FROM image - Fastgeom will preserve all of the FROM image using the + Fastgeom will preserve all of the FROM image using the translation computed from common space. This is comparable to map2map in that the whole from is projected using the - MATCH geometry. The size of the image is determined by - projecting the corners of the FROM image using the MATCH + MATCH geometry. The size of the image is determined by + projecting the corners of the FROM image using the MATCH geometry. Images can be quite large or small if there is a large resolution difference. @@ -1768,7 +2493,7 @@ End The - Sobel filter to both the FROM and MATCH images for + Sobel filter to both the FROM and MATCH images for enhanced edge detection. The 8-bit images are first scaled to 16-bit and a Gaussian Blur noise reduction filter is applied before the 3x3 Sobel filter is applied. The BORDER_REFLECT @@ -1778,12 +2503,11 @@ End @@ -1902,7 +2626,7 @@ End Only create a ground network file for dead reckoning - Create a control network with only the FROM image so that + Create a control network with only the FROM image so that only its pointing will be updated. @@ -1911,13 +2635,12 @@ End string - Specify which input file provides lat/lons for control - point + Specify which input file provides lat/lons for control point For input files that provide geometry, specify which one provides the latitude/longitude values for each control point. NONE is an acceptable option for which there is no geometry available. - Otherwise, the user must choose FROM or MATCH as the cube file + Otherwise, the user must choose FROM or MATCH as the cube file that wil provide geometry. MATCH @@ -1928,7 +2651,7 @@ End Use this option if geometry is not available in either - the FROM or MATCH image + the FROM or MATCH image + @@ -2095,9 +2833,11 @@ End result is shown in the main application documention. And here is the screen shot of qnet for the resulting network:

+

qnet result of first control point + Show all the available algorithms and their default parameters @@ -2112,7 +2852,7 @@ This provides a reference for all the current algorithms and their default param
 Object = Algorithms
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = AGAST
     Type         = Feature2D
     Features     = Detector
@@ -2131,7 +2871,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = AKAZE
     Type         = Feature2D
     Features     = (Detector, Extractor)
@@ -2153,7 +2893,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = Blob
     Type         = Feature2D
     Features     = Detector
@@ -2188,7 +2928,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = BRISK
     Type         = Feature2D
     Features     = (Detector, Extractor)
@@ -2206,7 +2946,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = FAST
     Type         = Feature2D
     Features     = Detector
@@ -2225,7 +2965,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = GFTT
     Type         = Feature2D
     Features     = Detector
@@ -2247,7 +2987,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = KAZE
     Type         = Feature2D
     Features     = (Detector, Extractor)
@@ -2268,7 +3008,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = MSD
     Type         = Feature2D
     Features     = Detector
@@ -2293,7 +3033,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = MSER
     Type         = Feature2D
     Features     = Detector
@@ -2317,7 +3057,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = ORB
     Type         = Feature2D
     Features     = (Detector, Extractor)
@@ -2341,7 +3081,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = SIFT
     Type         = Feature2D
     Features     = (Detector, Extractor)
@@ -2362,7 +3102,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = Star
     Type         = Feature2D
     Features     = Detector
@@ -2383,28 +3123,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
-    Name         = SURF
-    Type         = Feature2D
-    Features     = (Detector, Extractor)
-    Description  = "The OpenCV SURF Feature2D detector/extractor algorithm.
-                    See the documentation at
-                    http://docs.opencv.org/3.1.0/d5/df7/classcv_1_1xfeatures2d-
-                    _1_1SURF.html"
-    CreatedUsing = surf
-    Aliases      = (detector.surf, extractor.surf, feature2d.surf, surf)
-
-    Group = Parameters
-      Extended         = No
-      HessianThreshold = 100.0
-      NOctaveLayers    = 3
-      NOctaves         = 4
-      Upright          = No
-    End_Group
-  End_Object
-
-  Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = Brief
     Type         = Feature2D
     Features     = Extractor
@@ -2422,7 +3141,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = DAISY
     Type         = Feature2D
     Features     = Extractor
@@ -2446,7 +3165,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = FREAK
     Type         = Feature2D
     Features     = Extractor
@@ -2467,7 +3186,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = LATCH
     Type         = Feature2D
     Features     = Extractor
@@ -2486,7 +3205,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = LUCID
     Type         = Feature2D
     Features     = Extractor
@@ -2504,7 +3223,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = BFMatcher
     Type         = DecriptorMatcher
     Features     = Matcher
@@ -2522,7 +3241,7 @@ Object = Algorithms
   End_Object
 
   Object = Algorithm
-    CVVersion    = 3.1.0
+    CVVersion    = 4.5.5
     Name         = FlannBasedMatcher
     Type         = DecriptorMatcher
     Features     = Matcher
@@ -2544,6 +3263,390 @@ End
 	
- + + + Process three images using the default radial FASTGEOM option + + +

+ This example is a variation of example 1 that runs the images in + a different way. This example uses the FASTGEOM option with the + default settings applied to the same images. There is an additional + image included that has no overlap with the MATCH image. When + FASTGEOM is used, all images that cannot be mapped using ISIS + geometry are excluded from the matching process. If a filename + is provided in TONOGEOM option, images that do not have any + common mapping points generated by the FASTGEOM pattern algorithm + are written/recorded in this file. Images that are included in + the matching process but do not have any control measures in common + with the MATCH image will be recorded in the TONOTMATCHED filename + if given. +

+

+ The following command was used to create the control network of + the same two images, excluding the non-overlapping image, as was + processed in example 1: +

+
+findfeatures algorithm='fastx@threshold:25@type:2/brief/parameters@maxpoints:500' \
+             match=EW0211981114G.cub \
+             fromlist=fromlist.lis \
+             fastgeom=true \
+             geomtype=camera \
+             geomsource=both \
+             fastgeompoints=25 \
+             epitolerance=3.0 \
+             ratio=0.99 \
+             hmgtolerance=3.0 \
+             globals='FastGeomDumpMapping:true@SaveRenderedImages:true' \
+             networkid="EW0211981114G_EW0242463603G" \
+             pointid='EW211981116G_????' \
+             onet=EW0211981114G.net \
+             tolist=EW0211981114G_cubes.lis \
+             tonotmatched=EW0211981114G_notmatched.lis \
+             tonogeom=EW0211981114G_nogeom.lis \
+             description='Create image-image control network' \
+             debug=true \
+             debuglog=EW0211981114G.debug.log
+
+

+ In this case, since we are matching more than one file, all the + files to be matched must be listed, one per line, in the file + specified in the FROMFILE, fromlist.lis. Here is the contents + of this file: +

+EW0242463603G.cub
+EW0218118239G.cub
+
+

+

+ There are two additional parameters added in GLOBALS parameter. The + parameter FastGeomDumpMapping:true will notify the radial + mapping algorithm to dump a CSV file containing the results of the + common mapping points generated from the radial pattern. Recall that + the radial algorithm produces only one pattern from which it + calculates the perspective warping matrix for each image + in the FROMLIST. This option generates a file containing the data + made up of the basenames of the MATCH and FROMLIST image pairs. In + this example, the file EW0211981114G_EW0242463603G.radial.fastgeom.csv + is created. Even files that cannot be mapped will have a file + generated but they will be empty. + This matrix is then applied to the FROMLIST image + that is intended to better correspond spatially with the MATCH + image. The SaveRenderedImages:true parameter tells + findfeatures to save the warped images to a PNG file. This + will allow users to evaluate how well the FASTGEOM algorithm + performed. This option creates PNG files generated from their + base file names, such as EW0211981114G_query.png from the + MATCH file and EW0242463603G_train.png from the FROMLIST + file name. The processing of FASTGEOM is logged in the DEBUGLOG + file if given and DEBUG=true is set. Here is the portion of the + log file EW0211981114G.debug.log that is created from the + FASTGEOM algorithm: +

+
+---------------------------------------------------
+Program:        findfeatures
+Version         1.2
+Revision:       2023-06-16
+RunTime:        2023-06-19T11:44:03
+OpenCV_Version: 4.5.5
+
+System Environment...
+Number available CPUs:     8
+Number default threads:    8
+Total threads:             8
+
+Image load started at  2023-06-19T11:44:03
+
+++++ Running FastGeom ++++
+*** QueryImage: EW0211981114G.cub
+*** TrainImage: EW0242463603G.cub
+  FastGeomAlgorithm:            radial
+  FastGeomPoints:               25
+  FastGeomTolerance:            3
+  FastGeomQuerySampleTolerance: 0
+  FastGeomQueryLineTolerance:   0
+  FastGeomTrainSampleTolerance: 0
+  FastGeomTrainLineTolerance:   0
+
+--> Using Radial Algorithm train-to-query mapping <--
+  FastGeomMaximumRadius:         724.077
+  FastGeomRadialSegmentLength:   25
+  FastGeomRadialPointCount:      5
+  FastGeomRadialPointFactor:     1
+  FastGeomRadialSegments:        29
+
+==> Radial Point Mapping complete <==
+  TotalPoints:     2031
+  ImagePoints:     1333
+  MappedPoints:    1333
+  InTrainMapFOV:   636
+
+--> Dumping radial points <---
+  PointDumpFile:     EW0211981114G_EW0242463603G.radial.fastgeom.csv
+  TotalPoints:       1333
+
+==> Geometric Correspondence Mapping complete <==
+  TotalPoints:       636
+
+--> Running Homography Image Transform <---
+  IntialPoints:       636
+  Tolerance:          3
+  TotalLmedsInliers:  563
+  PercentPassing: 88.522
+
+  MatrixTransform:
+    0.645981,-0.0158572,113.771
+    -0.0350108,0.628872,337.353
+    -8.52086e-05,2.53351e-06,1
+
+
+++++ Running FastGeom ++++
+*** QueryImage: EW0211981114G.cub
+*** TrainImage: EW0218118239G.cub
+  FastGeomAlgorithm:            radial
+  FastGeomPoints:               25
+  FastGeomTolerance:            3
+  FastGeomQuerySampleTolerance: 0
+  FastGeomQueryLineTolerance:   0
+  FastGeomTrainSampleTolerance: 0
+  FastGeomTrainLineTolerance:   0
+
+--> Using Radial Algorithm train-to-query mapping <--
+  FastGeomMaximumRadius:         724.077
+  FastGeomRadialSegmentLength:   25
+  FastGeomRadialPointCount:      5
+  FastGeomRadialPointFactor:     1
+  FastGeomRadialSegments:        29
+
+==> Radial Point Mapping complete <==
+  TotalPoints:     2031
+  ImagePoints:     1333
+  MappedPoints:    0
+  InTrainMapFOV:   0
+
+--> Dumping radial points <---
+  PointDumpFile:     EW0211981114G_EW0218118239G.radial.fastgeom.csv
+  TotalPoints:       0
+
+==> Geometric Correspondence Mapping complete <==
+  TotalPoints:       0
+>>>> ERROR - Failed to get FOV geometry mapping for EW0218118239G.cub to EW0211981114G.cub needing 25 but got 0 in train FOV.
+Failed to load EW0218118239G.cub
+
+Image load complete at 2023-06-19T11:44:04
+
+Total failed image loads/FastGeoms excluded: 1
+EW0218118239G.cub
+
+See also EW0211981114G_nogeom.lis
+
+Total Algorithms to Run:     1
+
+@@ matcher-pair started on 2023-06-19T11:44:04
+
+

+ Note that an error is shown for the file EW0218118239G.cub. + This is because no common points were found for this file and the + MATCH file, EW0211981114G. Using the "keyword:value" pairs + in the GLOBALS parameters, the results can be analyzed as shown in + the panel below. The plot in the lower left corner of the panel + shows the results of the FASTGEOM Radial parameter geometry mapping + of common/cooresponding points of the MATCH and FROM image. The + points in red are outside the FROM image FOV and do not contribute + to the computation of the homography matrix for the perspective + warp for spatial consistency with the MATCH image. The blue + points are the ones used in to produce the homography matrix. + The image in the lower right corner is the result of the + warp of the FROM image. +

+

+ FASTEGEOM results using RADIAL algorithm +

+

+ The network produced from this example contains 145 control points + and with two measures each (290). This screen shot shows the + resulting network. The pointreg application should be run + on this network to refine the control measures to sub-pixel + accuracy. +

+

+ qnet result of first control point +

+
+
+ + + Create a controlled mosaic using findfeatures + + +

Overview

+

+ This example shows the results of a comprehensive processing sequence + that produces a controlled mosaic using findfeatures. The data + used for this mosaic is MESSENGER MDIS NAC and WAC EDR images of + Mercury taken from the MESSENGER MDIS PDS archive. These + EDRs are ingested into ISIS and standard processing is then + applied producing the regional controlled mosaic using + findfeatures. The EDR images used for this example are: +

+
+EN0108828436M.IMG
+EN0108828483M.IMG
+EN0108828488M.IMG
+EN0217733143M.IMG
+EN0217733334M.IMG
+EN0218118182M.IMG
+EW0215590428G.IMG
+EW0218075871G.IMG
+EW0218118239G.IMG
+        
+

+ It is important to recognize the procedures applied here are effective + under limited conditions. Namely, all the images included here + have some common overlap with the carefully chosen MATCH image. + Adding additional images that do not overlap the MATCH image will + be excluded by findfeatures and requires additional steps + that utilize techniques outside the scope of this example. + It would involve repeatedly running findfeatures where every + image is used as the MATCH image and all other images are included in + the FROMLIST (with common ground coverage) and the resulting + image-based networks are combined into a single network. This + technique is also used for larger, global networks that frequently + have images with largely varying resolutions and observing + conditions. This technique has successfully been used to create + global networks for Mercury using MESSENGER MDIS images and + Bennu using OSIRIS-REx OCAMS images. +

+

Making a Controlled Mosaic with findfeatures

+

+ The MESSENGER NAC and WAC images must be processed with the ISIS + application processing sequence of mdis2isis, spiceinit, + mdiscal, camstats and footprintinit. This will + prepare the images for findfeatures which will create an + image-based network. The findfeatures command used on these + nine images which performs image matching and creates the network is: +

+
+base="EW0218118239G"
+findfeatures algorithm='fastx@threshold:25@type:2/brief/parameters@Maxpoints:7000' \
+             match="${base}.cal.cub" \
+             fromlist=fromlist.lis \
+             fastgeom=true \
+             geomtype=camera \
+             geomsource=both \
+             fastgeompoints=50 \
+             epitolerance=7.0 \
+             ratio=0.99 \
+             hmgtolerance=7.0 \
+             globals='FastGeomDumpMapping:true@SaveRenderedImages:true@FastGeomAlgorithm:grid' \
+             networkid="${base}_Mosiac" \
+             pointid="${base}_????" \
+             onet="${base}.net" \
+             tolist="${base}_cubes.lis" \
+             tonotmatched="${base}_notmatched.lis" \
+             tonogeom="${base}_nogeom.lis" \
+             description='Create MESSENGER MDIS image-image control network' \
+             debug=true \
+             debuglog="${base}.debug.log"
+
+

+ There are several things to note. The maximum number of points to + retain from each image is limited in the detector (7000) as specified + in the "/parameter" section of the algorithm specification. The + fastx detector will very frequently produce a significant + number of keypoints. This may seem like a lot of keypoints per image + pair, but is likely a good choice for some images sets that contain + different pixel resolutions or largely varying observation conditions + like what is seen in this dataset. We are using the FASTGEOM Grid + algorithm (specified in the GLOBALS program option) because the + default Radial will have a difficult time meeting the minimum number + of FASTGEOMPOINTS points due to large variations in pixel + resolutions. The transformed files are retained so the FASTGEOM + performance can be evaluated. The HMGTOLERANCE and + EPITOLERANCE are both set to 7 pixels to account for + uncertainty in SPICE data and distortions in the translated images + due to the linear nature of the perspective warp transform caused + by topography differences (which should be accounted for in an + ensuing run of pointreg in the definition file by setting the + size of the SearchChip to be larger that twice the size of the + tolerances). We expect no files in the TONOGEOM + and TONOTMATCHED because no such problems exist in this + example. And you should have a look at the output DEBUGLOG + file for detailed performance and behavior information for + findfeatures that may indicate the need for adjustments of + the matching parameters. +

+

+ The MATCH image, EW0218118239G, was carefully chosen in this + dataset because it has common ground coverage/overlap with all the + other eight MDIS images. qmos is most helpful to determine + this image as a MATCH candidate as is shown in this screen + shot. Image EW0218118239G is the one outlined with the dashed square. + It is clear there are images with largely varying pixel resolutions. + Users will be able to select any image by changing the "base" + variable in prior to the execution of findfeatures. +

+

+ qmos of MESSENGER MDIS footprints +

+

+ The control network created by findfeatures is an image-based + network. This type of network will contain no points outside the FOV + of the MATCH image. In most cases when building a larger network, + a single image-based network is insufficient to produce a large + connected network, so successive runs of findfeatures using + different MATCH files is required. This image-based network contains + 3940 control points. The best networks will have the deepest control + points (i.e., contains many measures that includes all images + matched). Typically this is not realistic so additional + options can be considered. For example, success in jigsaw is + greatly increased by the quality of the control points/measures. + The pointreg application is used to refine control measures in + the output network produced by findfeatures to sub-pixel + accuracy. This step is where this result can be achieved by + setting a high Tolerance in the Algorithm group of + the REGDEF file. For great control points, use + Tolerance = 0.9, which is what was used in this example. + The network produced by findfeatures initially resulted in + 4646 control points with 12,203 control measures with a maximum + of 7 control measures in a control point. However, the average + number of measures was 2.6, which indicates the majority of + control points contain 2-3 measures - not so great. However, looking + carefully at the qmos data, there is not much potential for + deeper control points because of limited overlapping images. To + increase this number you can further increase HMGTOLERANCE + and EPITOLERANCE, or choose a different set of matching + algorithms. After pointreg, 3940 control points with 8338 + control measures remain, an average of 2.116 measures/point. This + is pretty good considering how high the Tolerance was set. The other + thing to consider is the distribution of the points. The screen shot + below shows the point distribution and density for each image. Given + the minimum coverage of many of the images, this will work - or at + least be worth producing a mosaic for evaluation. +

+

+ qnet of MESSENGER MDIS control network +

+

+ Producing a mosaic is the best way to assess the quality of the + network. It is recommended to create both an uncontrolled and + controlled mosaic to visually inspect/compare with qview or + an alternative image viewer. The screen shot below shows a portion + of the two mosaics. One important thing to note is this example does + not control to a ground truth source. There are at least several ways + to control your mosaic to ground truth but that is outside the + scope of this example (and they are typically a great deal of work!). + With that said, the spacecraft position serves as our "ground truth" + but you may also see the MATCH image will have the least adjustment + because it is highly biased since it has the most control measures. +

+

+ qview comparison of MESSENGER MDIS mosaic control region +

+
+
+
diff --git a/isis/tests/CnetWinnowTests.cpp b/isis/tests/CnetWinnowTests.cpp index 087f6c4ef5..d5e0dab249 100644 --- a/isis/tests/CnetWinnowTests.cpp +++ b/isis/tests/CnetWinnowTests.cpp @@ -21,7 +21,7 @@ static QString APP_XML = FileName("$ISISROOT/bin/xml/cnetwinnow.xml").expanded() TEST_F(ThreeImageNetwork, FunctionalTestCnetwinnowDefault) { QString onetPath = tempDir.path()+"/winnowedNetwork.net"; QVector args = {"onet="+onetPath, - "file_prefix=winnow"}; + "file_prefix=" + tempDir.path() + "/winnow"}; UserInterface ui(APP_XML, args); int initialMeasureCount = network->GetNumValidMeasures(); diff --git a/isis/tests/FunctionalTestsFindfeatures.cpp b/isis/tests/FunctionalTestsFindfeatures.cpp index e8a1c12f44..d820e87241 100644 --- a/isis/tests/FunctionalTestsFindfeatures.cpp +++ b/isis/tests/FunctionalTestsFindfeatures.cpp @@ -5,6 +5,7 @@ #include #include "NetworkFixtures.h" +#include "PvlFlatMap.h" #include "PvlGroup.h" #include "TestUtilities.h" #include "SurfacePoint.h" @@ -12,6 +13,7 @@ #include "Latitude.h" #include "Longitude.h" #include "SerialNumber.h" +#include "TextFile.h" #include "gmock/gmock.h" @@ -20,6 +22,37 @@ using ::testing::HasSubstr; static QString APP_XML = FileName("$ISISROOT/bin/xml/findfeatures.xml").expanded(); +// All FastGeom Keys expected in logs for algorithms +static const QStringList fastgeom_generic_keywords = { "FastGeomAlgorithm", + "FastGeomPoints", + "FastGeomTolerance", + "FastGeomQuerySampleTolerance", + "FastGeomQueryLineTolerance", + "FastGeomTrainSampleTolerance", + "FastGeomTrainLineTolerance" }; +static const QStringList fastgeom_radial_keywords = { "FastGeomMaximumRadius", + "FastGeomRadialSegmentLength", + "FastGeomRadialPointCount", + "FastGeomRadialPointFactor", + "FastGeomRadialSegments" }; +static const QStringList fastgeom_grid_keywords = { "FastGeomGridStartIteration", + "FastGeomGridStopIteration", + "FastGeomGridIterationStep", + "FastGeomGridSaveAllPoints", + "FastGeomPointIncrement", + "FastGeomTotalGridIterations" }; + + + +/** Helper function to load findfeatures debug log file */ +inline QStringList filter_strings( const std::vector &strlist, const QString &pattern ) { + QStringList found; + for ( auto const &line : strlist ) { + if ( line.contains( pattern ) ) found += line; + } + return ( found ); +} + TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesDefault) { // Setup output file QVector args = {"algorithm=brisk/brisk", @@ -215,7 +248,7 @@ TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesErrorNoInput) { FAIL() << "Should throw an exception" << std::endl; } catch (IException &e) { - EXPECT_THAT(e.what(), HasSubstr("**USER ERROR** Must provide both a FROM/FROMLIST and MATCH cube or image filename")); + EXPECT_THAT(e.what(), HasSubstr("**USER ERROR** Input cubes (0) failed to load. Must provide valid FROM/FROMLIST and MATCH cube or image filenames")); } } @@ -248,3 +281,274 @@ TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesErrorNoMatch) { EXPECT_THAT(e.what(), HasSubstr("**USER ERROR** No control points found!")); } } + +TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesFastGeomDefault) { + // Setup output file + const QString debuglogfile = tempDir.path() + "/default_fastgeom_algorithm.log"; + + // Needs no additional parameters to test the default case - just add log params + QVector args = {"algorithm=orb@hessianThreshold:100/orb", + "match=" + tempDir.path() + "/cube3.cub", + "from=" + tempDir.path() + "/cube2.cub", + "tolist=" + tempDir.path() + "/toList.txt", + "tonotmatched=" + tempDir.path() + "/unmatched.txt", + "maxpoints=5000", + "fastgeom=true", + "epitolerance=3.0", + "ratio=.9", + "hmgtolerance=3.0", + "onet=" + tempDir.path() + "/default_fastgeom_network.net", + "networkid=default_fastgeom", + "pointid=test_network_????", + "description=default_fastgeom", + "target=MARS", + "debug=true", + "debuglog=" + debuglogfile }; + UserInterface options(APP_XML, args); + findfeatures(options); + ControlNet network(options.GetFileName("ONET")); + + // Tests are based upon these condtions + ASSERT_EQ(network.GetNetworkId(), "default_fastgeom"); + ASSERT_EQ(network.Description().toStdString(), "orb@hessianThreshold:100/orb/BFMatcher@NormType:NORM_HAMMING@CrossCheck:false"); + ASSERT_EQ(network.GetNumPoints(), 30); + + // Load the log file and parse it looking for FastGeom signatures + std::vector logdata; + TextFile(debuglogfile, "input", logdata ); + QStringList fgeomkeys = filter_strings( logdata, "FastGeom" ); + + QStringList expected = fastgeom_generic_keywords + fastgeom_radial_keywords; + PvlFlatMap keyvalues; + + for ( auto const &key : expected ) { + QStringList parsed = fgeomkeys.filter( key ); + ASSERT_EQ( parsed.size(), 1 ); + if ( parsed.size() == 1 ) { + QStringList fgline = parsed[0].simplified().split(':'); + ASSERT_EQ( fgline.size(), 2 ); + ASSERT_EQ( fgline[0], key ); + if ( fgline.size() == 2 ) keyvalues.add(key, fgline[1].simplified()); + } + } + + // Check for expected values here + ASSERT_EQ( keyvalues.get( "FastGeomAlgorithm", "null"), "radial"); + ASSERT_EQ( keyvalues.get( "FastGeomPoints", "null"), "25"); + ASSERT_EQ( keyvalues.get( "FastGeomTolerance", "null"), "3"); + ASSERT_EQ( keyvalues.get( "FastGeomQuerySampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomQueryLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainSampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialSegmentLength", "null"), "25"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialPointCount", "null"), "5"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialPointFactor", "null"), "1"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialSegments", "null"), "14"); + + // Do the floating point special + EXPECT_NEAR( toDouble(keyvalues.get( "FastGeomMaximumRadius", "-1") ), 339.411, 1.0E-4); +} + +TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesFastGeomRadialConfig) { + // Setup output file + const QString debuglogfile = tempDir.path() + "/radial_config_fastgeom_algorithm.log"; + + // Needs no additional parameters to test the default case - just add log params + QVector args = {"algorithm=orb@hessianThreshold:100/orb", + "match=" + tempDir.path() + "/cube3.cub", + "from=" + tempDir.path() + "/cube2.cub", + "tolist=" + tempDir.path() + "/toList.txt", + "tonotmatched=" + tempDir.path() + "/unmatched.txt", + "parameters=" + radial_fastgeom_config, + "maxpoints=5000", + "fastgeom=true", + "epitolerance=3.0", + "ratio=.9", + "hmgtolerance=3.0", + "onet=" + tempDir.path() + "/radial_config_fastgeom_network.net", + "networkid=radial_config_fastgeom", + "pointid=test_network_????", + "description=radial_config_fastgeom", + "target=MARS", + "debug=true", + "debuglog=" + debuglogfile }; + UserInterface options(APP_XML, args); + findfeatures(options); + ControlNet network(options.GetFileName("ONET")); + + // Tests are based upon these condtions + ASSERT_EQ(network.GetNetworkId(), "radial_config_fastgeom"); + ASSERT_EQ(network.Description().toStdString(), "orb@hessianThreshold:100/orb/BFMatcher@NormType:NORM_HAMMING@CrossCheck:false"); + ASSERT_EQ(network.GetNumPoints(), 35); + + // Load the log file and parse it looking for FastGeom signatures + std::vector logdata; + TextFile(debuglogfile, "input", logdata ); + QStringList fgeomkeys = filter_strings( logdata, "FastGeom" ); + + QStringList expected = fastgeom_generic_keywords + fastgeom_radial_keywords; + PvlFlatMap keyvalues; + + for ( auto const &key : expected ) { + QStringList parsed = fgeomkeys.filter( key ); + ASSERT_EQ( parsed.size(), 1 ); + if ( parsed.size() == 1 ) { + QStringList fgline = parsed[0].simplified().split(':'); + ASSERT_EQ( fgline.size(), 2 ); + ASSERT_EQ( fgline[0], key ); + if ( fgline.size() == 2 ) keyvalues.add(key, fgline[1].simplified()); + } + } + + // Check for expected values here + ASSERT_EQ( keyvalues.get( "FastGeomAlgorithm", "null"), "radial"); + ASSERT_EQ( keyvalues.get( "FastGeomPoints", "null"), "25"); + ASSERT_EQ( keyvalues.get( "FastGeomTolerance", "null"), "3"); + ASSERT_EQ( keyvalues.get( "FastGeomQuerySampleTolerance", "null"), "15"); + ASSERT_EQ( keyvalues.get( "FastGeomQueryLineTolerance", "null"), "15"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainSampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialSegmentLength", "null"), "10"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialPointCount", "null"), "7"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialPointFactor", "null"), "0.5"); + ASSERT_EQ( keyvalues.get( "FastGeomRadialSegments", "null"), "37"); + + // Do the floating point special + EXPECT_NEAR( toDouble(keyvalues.get( "FastGeomMaximumRadius", "-1") ), 360.624, 1.0E-4); +} + +TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesFastGeomGridDefault) { + // Setup output file + const QString debuglogfile = tempDir.path() + "/grid_default_fastgeom_algorithm.log"; + + // Needs no additional parameters to test the default case - just add log params + QVector args = {"algorithm=orb@hessianThreshold:100/orb", + "match=" + tempDir.path() + "/cube3.cub", + "from=" + tempDir.path() + "/cube2.cub", + "tolist=" + tempDir.path() + "/toList.txt", + "tonotmatched=" + tempDir.path() + "/unmatched.txt", + "globals=FastGeomAlgorithm:grid", + "maxpoints=5000", + "fastgeom=true", + "epitolerance=3.0", + "ratio=.9", + "hmgtolerance=3.0", + "onet=" + tempDir.path() + "/grid_default_fastgeom_network.net", + "networkid=grid_default_fastgeom", + "pointid=test_network_????", + "description=grid_default_fastgeom", + "target=MARS", + "debug=true", + "debuglog=" + debuglogfile }; + UserInterface options(APP_XML, args); + findfeatures(options); + ControlNet network(options.GetFileName("ONET")); + + // Tests are based upon these condtions + ASSERT_EQ(network.GetNetworkId(), "grid_default_fastgeom"); + ASSERT_EQ(network.Description().toStdString(), "orb@hessianThreshold:100/orb/BFMatcher@NormType:NORM_HAMMING@CrossCheck:false"); + ASSERT_EQ(network.GetNumPoints(), 38); + + // Load the log file and parse it looking for FastGeom signatures + std::vector logdata; + TextFile(debuglogfile, "input", logdata ); + QStringList fgeomkeys = filter_strings( logdata, "FastGeom" ); + + QStringList expected = fastgeom_generic_keywords + fastgeom_grid_keywords; + PvlFlatMap keyvalues; + + for ( auto const &key : expected ) { + QStringList parsed = fgeomkeys.filter( key ); + ASSERT_EQ( parsed.size(), 1 ); + if ( parsed.size() == 1 ) { + QStringList fgline = parsed[0].simplified().split(':'); + ASSERT_EQ( fgline.size(), 2 ); + ASSERT_EQ( fgline[0], key ); + if ( fgline.size() == 2 ) keyvalues.add(key, fgline[1].simplified()); + } + } + + // Check for expected values here + ASSERT_EQ( keyvalues.get( "FastGeomAlgorithm", "null"), "grid"); + ASSERT_EQ( keyvalues.get( "FastGeomPoints", "null"), "25"); + ASSERT_EQ( keyvalues.get( "FastGeomTolerance", "null"), "3"); + ASSERT_EQ( keyvalues.get( "FastGeomQuerySampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomQueryLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainSampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomGridStartIteration", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomGridStopIteration", "null"), "239"); + ASSERT_EQ( keyvalues.get( "FastGeomGridIterationStep", "null"), "1"); + ASSERT_EQ( keyvalues.get( "FastGeomGridSaveAllPoints", "null"), "No"); + ASSERT_EQ( keyvalues.get( "FastGeomPointIncrement", "null"), "5"); + ASSERT_EQ( keyvalues.get( "FastGeomTotalGridIterations", "null"), "2"); +} + +TEST_F(ThreeImageNetwork, FunctionalTestFindfeaturesFastGeomGridConfig) { + // Setup output file + const QString debuglogfile = tempDir.path() + "/grid_config_fastgeom_algorithm.log"; + + // Needs no additional parameters to test the default case - just add log params + QVector args = {"algorithm=orb@hessianThreshold:100/orb", + "match=" + tempDir.path() + "/cube3.cub", + "from=" + tempDir.path() + "/cube2.cub", + "tolist=" + tempDir.path() + "/toList.txt", + "tonotmatched=" + tempDir.path() + "/unmatched.txt", + "parameters=" + grid_fastgeom_config, + "maxpoints=5000", + "fastgeom=true", + "epitolerance=3.0", + "ratio=.9", + "hmgtolerance=3.0", + "onet=" + tempDir.path() + "/grid_default_fastgeom_network.net", + "networkid=grid_config_fastgeom", + "pointid=test_network_????", + "description=grid_config_fastgeom", + "target=MARS", + "debug=true", + "debuglog=" + debuglogfile }; + UserInterface options(APP_XML, args); + findfeatures(options); + ControlNet network(options.GetFileName("ONET")); + + // Tests are based upon these condtions + ASSERT_EQ(network.GetNetworkId(), "grid_config_fastgeom"); + ASSERT_EQ(network.Description().toStdString(), "orb@hessianThreshold:100/orb/BFMatcher@NormType:NORM_HAMMING@CrossCheck:false"); + ASSERT_EQ(network.GetNumPoints(), 31); + + // Load the log file and parse it looking for FastGeom signatures + std::vector logdata; + TextFile(debuglogfile, "input", logdata ); + QStringList fgeomkeys = filter_strings( logdata, "FastGeom" ); + + QStringList expected = fastgeom_generic_keywords + fastgeom_grid_keywords; + PvlFlatMap keyvalues; + + for ( auto const &key : expected ) { + QStringList parsed = fgeomkeys.filter( key ); + ASSERT_EQ( parsed.size(), 1 ); + if ( parsed.size() == 1 ) { + QStringList fgline = parsed[0].simplified().split(':'); + ASSERT_EQ( fgline.size(), 2 ); + ASSERT_EQ( fgline[0], key ); + if ( fgline.size() == 2 ) keyvalues.add(key, fgline[1].simplified()); + } + } + + // Check for expected values here + ASSERT_EQ( keyvalues.get( "FastGeomAlgorithm", "null"), "grid"); + ASSERT_EQ( keyvalues.get( "FastGeomPoints", "null"), "25"); + ASSERT_EQ( keyvalues.get( "FastGeomTolerance", "null"), "3"); + ASSERT_EQ( keyvalues.get( "FastGeomQuerySampleTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomQueryLineTolerance", "null"), "0"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainSampleTolerance", "null"), "15"); + ASSERT_EQ( keyvalues.get( "FastGeomTrainLineTolerance", "null"), "15"); + ASSERT_EQ( keyvalues.get( "FastGeomGridStartIteration", "null"), "5"); + ASSERT_EQ( keyvalues.get( "FastGeomGridStopIteration", "null"), "10"); + ASSERT_EQ( keyvalues.get( "FastGeomGridIterationStep", "null"), "2"); + ASSERT_EQ( keyvalues.get( "FastGeomGridSaveAllPoints", "null"), "No"); + ASSERT_EQ( keyvalues.get( "FastGeomPointIncrement", "null"), "5"); + ASSERT_EQ( keyvalues.get( "FastGeomTotalGridIterations", "null"), "1"); +} + + diff --git a/isis/tests/NetworkFixtures.cpp b/isis/tests/NetworkFixtures.cpp index c3a860fb98..baa206a714 100644 --- a/isis/tests/NetworkFixtures.cpp +++ b/isis/tests/NetworkFixtures.cpp @@ -104,6 +104,9 @@ namespace Isis { cube1map->fromIsd(tempDir.path() + "/cube1map.cub", mappedLabelPath1, *isdPath1, "rw"); cube2map->fromIsd(tempDir.path() + "/cube2map.cub", mappedLabelPath2, *isdPath2, "rw"); cube3map->fromIsd(tempDir.path() + "/cube3map.cub", mappedLabelPath3, *isdPath3, "rw"); + + grid_fastgeom_config = "data/threeImageNetwork/grid_fastgeom_algorithm.pvl"; + radial_fastgeom_config = "data/threeImageNetwork/radial_fastgeom_algorithm.pvl"; } diff --git a/isis/tests/NetworkFixtures.h b/isis/tests/NetworkFixtures.h index d59437ed7b..c344c81a0f 100644 --- a/isis/tests/NetworkFixtures.h +++ b/isis/tests/NetworkFixtures.h @@ -37,6 +37,10 @@ namespace Isis { QString cubeListFile; QString twoCubeListFile; + // Optional use of FastGeom algorithms + QString grid_fastgeom_config; + QString radial_fastgeom_config; + std::vector> coords; void SetUp() override; diff --git a/isis/tests/data/threeImageNetwork/grid_fastgeom_algorithm.pvl b/isis/tests/data/threeImageNetwork/grid_fastgeom_algorithm.pvl new file mode 100644 index 0000000000..2448c66abb --- /dev/null +++ b/isis/tests/data/threeImageNetwork/grid_fastgeom_algorithm.pvl @@ -0,0 +1,15 @@ +Object = FastGeomParameters + + FastGeomAlgorithm = "Grid" + FastGeomPoints = 60 + + FastGeomTrainSampleTolerance = 15 + FastGeomTrainLineTolerance = 15 + + FastGeomGridStartIteration = 5 + FastGeomGridStopIteration = 10 + FastGeomGridIterationStep = 2 + +EndObject = FastGeomParameters +End + diff --git a/isis/tests/data/threeImageNetwork/radial_fastgeom_algorithm.pvl b/isis/tests/data/threeImageNetwork/radial_fastgeom_algorithm.pvl new file mode 100644 index 0000000000..23e9c60f75 --- /dev/null +++ b/isis/tests/data/threeImageNetwork/radial_fastgeom_algorithm.pvl @@ -0,0 +1,15 @@ +Object = FastGeomParameters + + FastGeomAlgorithm = "Radial" + FastGeomPoints = 50 + + FastGeomRadialPointCount = 7 + FastGeomRadialPointFactor = 0.5 + FastGeomRadialSegmentLength = 10 + + FastGeomQuerySampleTolerance = 15 + FastGeomQueryLineTolerance = 15 + +EndObject = FastGeomParameters +End +