diff --git a/Jenkinsfile b/Jenkinsfile index 421fc6a91f14ca822d7c275c85c17e19cdc86c76..e46dd80724c1d092c69d43632e4c200509fa8d50 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -48,7 +48,7 @@ pipeline { cd build cmake -GNinja -DJP2KFLAG=ON \ -DKAKADU_INCLUDE_DIR=${KAKADU_HEADERS} \ - -Dpybindings=OFF \ + -Dpybindings=ON \ -DCMAKE_BUILD_TYPE=RELEASE \ -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX} \ ../isis @@ -117,6 +117,8 @@ pipeline { cd $WORKSPACE/isis/pytests pytest . + cd $WORKSPACE/isis/python_bindings/tests + pytest . ''' } } diff --git a/environment.yml b/environment.yml index 329c72ce80ccdef83207594c1476358466c0c8ba..ed8af18403f35ffed95d4404c409868ca2e02034 100644 --- a/environment.yml +++ b/environment.yml @@ -3,6 +3,7 @@ channels: - defaults dependencies: + - kalasiris - ale=0.9.1 - armadillo - boost=1.72 @@ -48,6 +49,7 @@ dependencies: - opencv>=4.5.2 - openssl>=1.1.1k - pcl >= 1.10.0 + - plio - protobuf<3.20 - python>=3.7.11 - pytest diff --git a/isis/CMakeLists.txt b/isis/CMakeLists.txt index edd414fde877bc01935b83c7544375f93108b42f..0bb2e59308ae1ad1ca03d8dc5c1a34768cd62358 100644 --- a/isis/CMakeLists.txt +++ b/isis/CMakeLists.txt @@ -78,7 +78,7 @@ option(buildMissions "Build the mission specific modules" ON ) option(buildStaticCore "Build libisis static as well as dynamic" OFF ) option(buildTests "Set up unit, application, and module tests." ON ) option(JP2KFLAG "Whether or not to build using JPEG2000 support" OFF ) -option(pybindings "Turn on to build Python bindings" OFF ) +option(pybindings "Turn on to build Python bindings" ON ) # if cmake install prefix is not set, and conda env is activated, use the # conda env as the install directory. @@ -309,10 +309,6 @@ if(APPLE) find_package(OpenGL REQUIRED) endif(APPLE) -if(pybindings) - find_package(Python REQUIRED) -endif() - # Iterate through all variables and extract the libraries and include directories get_cmake_property(_variableNames VARIABLES) # Get All VARIABLES @@ -378,6 +374,9 @@ include_directories(${CMAKE_BINARY_DIR}/inc) set(CORE_LIB_NAME isis) message(STATUS "CORE LIB: ${CORE_LIB_NAME}") +if(pybindings) + add_subdirectory(python_bindings) +endif() # Specify relative library include paths which will be set up on # the installed files. diff --git a/isis/cmake/AddIsisModule.cmake b/isis/cmake/AddIsisModule.cmake index a0f696624b71bcbe403aa4be2a94325d422c7817..a7fbde7b6940893f16232de9e634950fc13a20ae 100644 --- a/isis/cmake/AddIsisModule.cmake +++ b/isis/cmake/AddIsisModule.cmake @@ -6,7 +6,6 @@ include(CodeGeneration) # Incorporate an application folder function(add_isis_app folder libDependencies) - # The internal build name will be different than the output name # - This deals with problems compiling same-named targets on case-insensitive machines. get_filename_component(appName ${folder} NAME) diff --git a/isis/python_bindings/CMakeLists.txt b/isis/python_bindings/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f07cb8c230858f5860aba67780c647039fc3d5f2 --- /dev/null +++ b/isis/python_bindings/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.14) + +# Setup for SWIG +set(CMAKE_SWIG_FLAGS) +find_package(SWIG REQUIRED) +include(UseSWIG) +list(APPEND CMAKE_SWIG_FLAGS "-py3;-DPY3") + +message(STATUS "SWIG flags: " ${CMAKE_SWIG_FLAGS} ) + +# Setup for Python linking +find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + +# Setup for wrapper library +set(ASTROSET_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/astroset") +set(ASTROSET_SOURCES astroset.i + UserInterface.i + ) + +set(ASTROSET_APPS apps/) + +set_source_files_properties(${ASTROSET_SOURCES} PROPERTIES CPLUSPLUS ON) +swig_add_library(astroset + LANGUAGE python + SOURCES ${ASTROSET_SOURCES} + OUTPUT_DIR ${ASTROSET_OUTPUT_DIR}) +swig_link_libraries(astroset isis Python3::Module) +set_target_properties(astroset PROPERTIES SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE) +set_target_properties(astroset PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${ASTROSET_OUTPUT_DIR}) + +# Create the files to install the Python wrapper +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in + ${CMAKE_CURRENT_BINARY_DIR}/setup.py) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/__init__.py + ${ASTROSET_OUTPUT_DIR}/__init__.py + COPYONLY) + +file(COPY ${ASTROSET_APPS} + DESTINATION + ${ASTROSET_OUTPUT_DIR}/apps/) + +set(ASTROSET_APP_XML_FILES "apps/findFeaturesSegment/findFeaturesSegment.xml") +file(COPY ${ASTROSET_APP_XML_FILES} DESTINATION ${CMAKE_BINARY_DIR}/bin/xml/ ) +install(FILES ${ASTROSET_APP_XML_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin/xml/) + +# Setup to run setup tools on install +install(CODE "execute_process(COMMAND $ENV{CONDA_PREFIX}/bin/pip install ${CMAKE_CURRENT_BINARY_DIR}/ --prefix=${CMAKE_INSTALL_PREFIX} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})") + diff --git a/isis/python_bindings/UserInterface.i b/isis/python_bindings/UserInterface.i new file mode 100644 index 0000000000000000000000000000000000000000..cdef23a5386569c5bd156547b2acb4cef1894c38 --- /dev/null +++ b/isis/python_bindings/UserInterface.i @@ -0,0 +1,42 @@ +%module(package="astroset") UserInterface + + +%{ + #include "Gui.h" + #include "Pvl.h" + #include "UserInterface.h" +%} + +%include +%include + +// Rename the constructors to Python-friendly names +%rename(UserInterfaceFromArgsv) UserInterface(const QString &xmlfile, int &argc, char *argv[]); +%rename(UserInterfaceFromList) UserInterface(const QString &xmlfile, QVector &args); + +%rename(GetFileName) IsisAml::GetFileName(QString const &) const; +%rename(GetFileNameWithExt) IsisAml::GetFileName(QString const &, QString) const; +%rename(GetFileNames) IsisAml::GetFileName(QString const &, std::vector &) const; + +%rename(GetAsString) IsisAml::GetAsString(QString const &) const; +%rename(GetAsStrings) IsisAml::GetAsString(QString const &, std::vector &) const; + +%rename(GetString) IsisAml::GetString(QString const &) const; +%rename(GetStrings) IsisAml::GetString(QString const &, std::vector &) const; + +%rename(GetCubeNameWithExt) IsisAml::GetCubeName(QString const &,QString) const; +%rename(GetCubeName) IsisAml::GetCubeName(QString const &) const; + +%rename(GetInteger) IsisAml::GetInteger(const QString &) const; +%rename(GetIntegers) IsisAml::GetInteger(const QString &, std::vector &) const; + +%rename(GetDouble) IsisAml::GetDouble(const QString &) const; +%rename(GetDoubles) IsisAml::GetDouble(const QString &, std::vector &) const; + +%rename(GetBoolean) IsisAml::GetBoolean(const QString &) const; +%rename(GetBooleans) IsisAml::GetBoolean(const QString &, std::vector &) const; + +%include "Pvl.h" +%include "IsisAml.h" +%include "UserInterface.h" + diff --git a/isis/python_bindings/__init__.py b/isis/python_bindings/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..530a29dda8765cfdd6a1f6ade46aa5b97c0d21d7 --- /dev/null +++ b/isis/python_bindings/__init__.py @@ -0,0 +1,22 @@ +from .astroset import * + +from pathlib import Path +import os + +try: + ISISROOT = Path(os.environ["ISISROOT"]) +except KeyError: + raise EnvironmentError("Please set ISISROOT before importing anything") + +def init_application(args): + appname = os.path.basename(args[0]) + args = args[1:] + + xmlpath = Path(appname).with_suffix(".xml") + if not xmlpath.exists() or not xmlpath.is_file(): + xmlpath = ISISROOT / "bin" / "xml" / xmlpath + print(xmlpath) + if not xmlpath.exists(): + raise FileExistsError(f"{appname} does not have an XML file") + + return UserInterfaceFromList(str(xmlpath), args) \ No newline at end of file diff --git a/isis/python_bindings/apps/__init__.py b/isis/python_bindings/apps/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..0bdd7af264b5ea8f9877e90a4f3a81a97d614d18 --- /dev/null +++ b/isis/python_bindings/apps/__init__.py @@ -0,0 +1 @@ +from . import findFeaturesSegment \ No newline at end of file diff --git a/isis/python_bindings/apps/findFeaturesSegment/__init__.py b/isis/python_bindings/apps/findFeaturesSegment/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py new file mode 100755 index 0000000000000000000000000000000000000000..da702fba504638e36319608094c1d5818888f36d --- /dev/null +++ b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python + +import os +import sys +import re + +import kalasiris as kisis + +import subprocess +import pvl + +from glob import glob +import logging +from pathlib import Path +import logging as log +from copy import deepcopy +import tempfile + +import plio +from plio.io import io_controlnetwork as cnet +from plio.io import isis_serial_number as sn + +from subprocess import check_output +from multiprocessing.pool import ThreadPool +import multiprocessing +import itertools +from functools import reduce +from subprocess import check_output +from itertools import compress +from math import ceil + +import astroset + +MAX_LEN = 30000 + +# remove progress bar output from string +filter_progress = lambda x: "\n".join([line for line in x.split("\n") if not "% Processed" in line and not "Working" in line]) + + +def footprintinit(img : str): + """ + Just a wrapper for footprintinit with logging and exception handling + Probably should be a functor in kalasiris + """ + try: + ret = kisis.footprintinit(img) + log.debug(f"returned: {ret}") + return ret + except subprocess.CalledProcessError as err: + log.debug('Had an ISIS error:') + log.debug(' '.join(err.cmd)) + log.debug(err.stdout) + log.debug(err.stderr) + + +def read_cubelist(cube_list : Path): + """ + read_cubelist accepts a filelist path and returns a list object + + Parameters + ---------- + cube_list : Path + path-like object pointing to filelist + + Returns + ------- + list + list of files + """ + with open(cube_list) as c: + content = c.read() + content = content.strip() + files = content.split("\n") + return files + + +def segment(img_path : Path, nlines : int = MAX_LEN): + """ + Segments an image into multiple parts. + + Parameters + ---------- + img_path : path-like + The path to the image to be segmented. + nlines : int + The number of lines in each segment (defaults to MAX_LEN). + + Returns + ------- + segment_metadata : list of dict + A list of dictionaries containing metadata for each segment. + """ + + if isinstance(img_path, str): + img_path = Path(img_path) + log.debug(f"nlines: {nlines}") + + segment_metadata = {} + try: + ret = kisis.segment(img_path, nl=nlines, overlap=0, pref__="$ISISROOT/IsisPreferences") + log.debug(f"{ret}") + segment_metadata = pvl.loads(filter_progress(ret.stdout)) + + # comes nested in a "results" group, trim it off + segment_metadata = [s[1] for s in segment_metadata] + + glob_str = str(img_path.parent / img_path.stem) + ".segment*" + log.debug(f"globbing segments: glob({glob_str})") + segments = sorted(glob(glob_str)) + + log.debug(f"segments: {segments}") + + i = 0 + for s, meta in zip(segments, segment_metadata): + i += 1 + meta["Path"] = s + serial = kisis.getsn(s).stdout.split("\n")[0].strip() + log.debug(f"sn: {serial}") + meta["SN"] = serial + meta["Segment"] = i + meta["Original"] = str(img_path) + + seg_dict_keys = [f"seg{n}" for n in range(1,len(segments)+1)] + segment_dict = dict(zip(seg_dict_keys, segment_metadata)) + log.debug(f"Segment dict: {segment_dict}") + except subprocess.CalledProcessError as err: + print('Had an ISIS error:') + print(' '.join(err.cmd)) + print(err.stdout) + print(err.stderr) + raise err + return segment_dict + + +def generate_cnet(params, images): + + match_segment_n = images["match"]["Segment"] + from_segment_n = images["from"][0]["Segment"] + + new_params = deepcopy(params) + new_params.pop("NL") + new_params.pop("MINAREA") + new_params.pop("MINTHICKNESS") + + # make sure none of these keys are still in the params + new_params.pop("FROMLIST", None) + new_params.pop("FROM", None) + new_params["MATCH"] = images["match"]["Path"] + + og_onet = Path(params["ONET"]) + og_tolist = Path(params["TOLIST"]) + og_pointid = params["POINTID"] + og_networkid = params["NETWORKID"] + + from_images = [image["Path"] for image in images["from"]] + starting_lines = [image["StartingLine"] for image in images["from"]] + log.debug(f"from images: {from_images}") + + match_stem = Path(new_params["MATCH"]).stem + + fromlist_path = Path(og_tolist.parent / f"from_images_segment{from_segment_n}.lis") + from_stem = fromlist_path.stem + + if "debuglog" in new_params: + og_log = Path(new_params["DEBUGLOG"]) + new_log = og_log.parent / f"{og_log.stem}.{match_stem}_{from_stem}{og_log.suffix}" + new_params["DEBUGLOG"] = str(new_log) + + new_params["NETWORKID"] = og_networkid + f"_{match_segment_n}_{from_stem}" + new_params["POINTID"] = og_pointid + f"_{match_segment_n}_{from_stem}" + new_params["ONET"] = f"{og_onet.parent/og_onet.stem}_{match_segment_n}_{from_stem}.net" + new_params["TOLIST"] = f"{og_tolist.parent/og_tolist.stem}_{match_segment_n}_{from_stem}.lis" + + log.debug(new_params) + + # check for overlaps + is_overlapping = [] + for image in from_images: + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + overlapfromlist = tmpdir / "fromlist.lis" + overlaptolist = tmpdir / "tolist.lis" + kisis.fromlist.make([*from_images, new_params["MATCH"]], overlapfromlist) + + try: + kisis.findimageoverlaps(fromlist=overlapfromlist, overlaplist=overlaptolist) + except subprocess.CalledProcessError as err: + print('Had an ISIS error:') + print(' '.join(err.cmd)) + print(err.stdout) + print(err.stderr) + raise err + + ret = kisis.overlapstats(fromlist=overlapfromlist, overlaplist=overlaptolist, pref__="$ISISROOT/IsisPreferences") + stats = pvl.loads(filter_progress(ret.stdout)) + log.debug(f"overlap stats: {ret.stdout}") + + # first, throw it out if there is no overlap whatsoever + is_pair_overlapping = not any([k[1].get("NoOverlap", "") == new_params["MATCH"] for k in stats]) + + if is_pair_overlapping: + is_thick_enough = stats["Results"]["ThicknessMinimum"] > float(params.get("MINTHICKNESS", 0)) + is_area_large_enough = stats["Results"]["AreaMinimum"] > float(params.get("MINAREA", 0)) + is_pair_overlapping = all([is_thick_enough, is_area_large_enough]) + is_overlapping.append(is_pair_overlapping) + else: # not overlapping + is_overlapping.append(False) + + # mask images + from_images = list(compress(from_images, is_overlapping)) + log.debug(f"From images overlapping Match: {from_images}") + + + if from_images: + + log.debug(f"FROMLIST: {from_images}") + + if not fromlist_path.exists(): + log.debug(f"writing to: {fromlist_path}") + kisis.fromlist.make(from_images, fromlist_path) + else: + log.debug(f"{fromlist_path} already exists") + new_params["FROMLIST"] = str(fromlist_path) + + try: + ret = kisis.findfeatures(**new_params) + log.debug(f"returned: {ret}") + except subprocess.CalledProcessError as err: + log.debug('Had an ISIS error:') + log.debug(' '.join(err.cmd)) + log.debug(err.stdout) + log.debug(err.stderr) + + segmented_net = cnet.from_isis(new_params["ONET"]) + + # starting sample in inclusive, so subtract 1 + segmented_net.loc[segmented_net.serialnumber == images["match"]["SN"], "line"] += images["match"]["StartingLine"]-1 + + # offset the images + for k, image in enumerate(images["from"]): + image_sn = image["SN"] + + # starting sample is inclusive, so subtract 1 + segmented_net.loc[segmented_net.serialnumber == image_sn, "line"] += starting_lines[k]-1 + cnet.to_isis(segmented_net, new_params["ONET"], targetname="moon") + + from_originals = [image["Original"] for image in images["from"]] + return {"onet": new_params["ONET"], "original_images": from_originals} + + +def merge(d1, d2, k): + """ + Merge two dictionary keys together such that values + always comes back as a list when keys overlap. + """ + # probably a cleaner way to do this + + v1 = d1.get(k, None) + v2 = d2.get(k, None) + + if not v1: + return v2 if isinstance(v2, list) else [v2] + if not v2: + return v1 if isinstance(v1, list) else [v1] + if isinstance(v1, list) and not isinstance(v2, list): + return v1+[v2] + if not isinstance(v1, list) and isinstance(v2, list): + return [v1]+v2 + if not isinstance(v1, list) and not isinstance(v2, list): + return [v1]+[v2] + if isinstance(v1, list) and isinstance(v2, list): + return v1+v2 + + +def findFeaturesSegment(ui): + """ + findFeaturesSegment Calls FindFeatures on segmented images + + findFeaturesSegment works by splitting the MATCH and FROM images into segments defined by NL with 0 + pixel overlaps. The match image segments are then matched with every FROM/FROMLIST image semgnet that overlap + enough as defined by max/min overlap and area parameters. + + Parameters + ---------- + ui : astroset.UserInterface + UserInterface object containing user parameters + images : dict + dictionary of images to match. Format dictated by segment. + + Returns + ------- + dict + dictionary containing output cnet and image list + """ + if ui.GetBoolean("debug"): + log.basicConfig(level=logging.DEBUG) + else: + log.basicConfig(level=logging.INFO) + + img_list = [] + if ui.WasEntered("From"): + img_list = [ui.GetFileName("From")] + elif ui.WasEntered("FromList"): + img_list = read_cubelist(ui.GetFileName("FromList")) + else: + raise ValueError('**User Error** invalid command Line. Missing Parameter "from" or "fromlist"') + + # Segment things + if ui.WasEntered("maxthreads"): + nthreads = ui.GetInteger("maxthreads") + else: + nthreads = int(multiprocessing.cpu_count()) + + + pool = ThreadPool(ceil(nthreads/len(img_list))) + output = pool.map_async(segment, img_list) + pool.close() + pool.join() + output = output.get() + + match_segments = segment(ui.GetCubeName("match"), ui.GetInteger("NL")) + from_segments = reduce(lambda d1,d2: {k: merge(d1, d2, k) for k in set(d1)|set(d2)}, output) + segment_paths = [s["Path"] for sublist in list(from_segments.values()) for s in sublist] + segment_paths = segment_paths + [s["Path"] for s in list(match_segments.values())] + + # re-generate footprints + pool = ThreadPool(ceil(nthreads/len(segment_paths))) + output = pool.map_async(footprintinit, segment_paths) + pool.close() + pool.join() + output = output.get() + log.debug(f"{output}") + + image_sets = list(itertools.product(match_segments.values(), from_segments.values())) + + # restructure things to be easier to manage + job_dicts = [] + for im in image_sets: + match = im[0] + from_images = im[1] + job_dicts.append({ + "match" : match, + "from" : from_images + }) + + # get params as a dictionary + params = ui.GetParams() + + pool = ThreadPool(ceil(nthreads/len(job_dicts))) + starmap_args = list(zip([params]*len(job_dicts), job_dicts)) + output = pool.starmap_async(generate_cnet, starmap_args) + pool.close() + pool.join() + output = output.get() + log.debug(f"output: {output}") + + # merge the networks + onets = [o["onet"] for o in output if isinstance(o, dict)] + log.debug(f"onets: {onets}") + onet_list = Path(ui.GetFileName("onet")).with_suffix(".segmented.lis") + kisis.fromlist.make(onets, onet_list) + + # merge the filelists + tolists = [set(o["original_images"]) for o in output if isinstance(o, dict)] + + final_images = set.union(*tolists) + final_images.add(ui.GetCubeName("match")) + + log.debug(f"merged images: {final_images}") + kisis.fromlist.make(final_images, Path(ui.GetFileName("tolist"))) + + try: + kisis.cnetmerge(clist = onet_list, onet=ui.GetFileName("onet"), networkid=ui.GetAsString("networkid"), description=f"{ui.GetString('description')}") + except subprocess.CalledProcessError as err: + log.debug('Had an ISIS error:') + log.debug(' '.join(err.cmd)) + log.debug(err.stdout) + log.debug(err.stderr) + + +if __name__ == "__main__": + ui = astroset.init_application(sys.argv) + findFeaturesSegment(ui) + diff --git a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml new file mode 100644 index 0000000000000000000000000000000000000000..54ebc5f1cfd3f0d7e1ce14595f8c11bef295cee0 --- /dev/null +++ b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml @@ -0,0 +1,1435 @@ + + + + + + Feature-based matching algorithms used to create ISIS control networks with segmented images. + + + +

Overview

+ +

+ findFeaturesSegment is developed on top of findfeatures as a way to overcome + findfeatures image size limitations. This is done by segmenting the images and running + findfeatures on the sub images. See the documentation for findfeatures for more + information on how that application works as findFeaturesSegment works with the same matching + parameters. +

+ +

+ findFeaturesSegment works by splitting the MATCH and FROM images into segments defined by NL with 0 + pixel overlaps. The match image segments are then matched with every FROM/FROMLIST image semgnet that overlap + enough as defined by max/min overlap and area parameters. +

+
+ + + Registration and Pattern Matching + Control Networks + + + + + + + + + + cube + input + + Input Image to be Translated + + + 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 + + imread()routine is supported. All input images are converted to + 8-bit when they are read. + + + *.cub + + None + + + + filename + input + + List of input cubes/images for which to create a control network + + +

+ Use this parameter to select a filename which contains a list of + 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: +

+
+            AS15-M-0582_16b.cub
+            AS15-M-0583_16b.cub
+            AS15-M-0584_16b.cub
+            AS15-M-0585_16b.cub
+            AS15-M-0586_16b.cub
+            AS15-M-0587_16b.cub
+          
+

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

+
+ None + + *.lis + +
+ + + cube + input + + The input reference image (query) + + + Name of the image to match to. This will be the reference image in + the output control network. It is also referred to as the query + image in OpenCV documentation. All input images are converted to + 8-bit when they are read. + + + *.cub + + None + + + + filename + output + 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 + 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. + + None + + *.net *.txt + + + + + filename + output + Output list of ControlNet network cube files + + 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 + 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. + + None + *.lis + + + + integer + output + Number of Lines for each new cube segment + + Specifies the total number of lines of overlap each segment will have with the previous segment as well as the next segment. + + 30000 + 3 + 30000 + + + + integer + output + Number of Lines for each new cube segment + + Specifies the minimum area of overlapping images + + 0 + + + + integer + output + Number of Lines for each new cube segment + + Specifies the minimum thickness of overlapping images + + 0 + + + + filename + output + + Output list of FROM/FROMLIST cube files that were not + successfully matched + + +

+ This file will contain the list of (cube) files that were not + successfully matched. This can be used to run through individually + with more specifically tailored matcher algorithm specifications. +

+

+ NOTE this file is appended to so that continual runs will + accumulate failures making it easier to handle failed runs. +

+
+ None + *.lis +
+
+ + + + string + + Provide one or more algorithm specifications to apply + + + This parameter provides user control over selecting a wide + variety of feature detectors, extractors and matcher + combinations. This parameter also provides a mechanism to set + any of the valid parameters of the algoritms. + + None + + + + filename + + Provide one or more algorithms in a text file + + + 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 + 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 + elements of the feature algorithm. + + None + *.lis + + + + boolean + + List result of ALGORITHM specification + + + 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. + + No + + + + boolean + + List all OpenCV algorithms irregardles of origin + + + This parameter will retrieve all the registered OpenCV + algorithms available that can created by name. + + No + + + + filename + + Optional output file/device to write information requests to + + +

+ When an information option is requested (LISTSPEC), + the user can provide the name of an output file here where the + information, in the form of a PVL structure, will be written. + If any of those options are selected by the user, and + a file is not provided in this option, the output is written + to the screen or GUI. +

+

+ One very nifty option that works well is to specify the + terminal device as the output file. This will list the + results to the screen so that your input can be quickly + checked for accuracy. Here is an example using the algorithm + listing option and the result: +

+ + findfeatures listspec=true + algorithm=detector.Blob@minrepeatability:1/orb + toinfo=/dev/tty + +

+Object = FeatureAlgorithms
+  Object = RobustMatcher
+    OpenCVVersion = 3.1.0
+    Name          = detector.Blob@minrepeatability:1/orb/BFMatcher@NormType:N-
+                    ORM_HAMMING@CrossCheck:false
+
+    Object = Detector
+      CVVersion    = 3.1.0
+      Name         = Blob
+      Type         = Feature2D
+      Features     = Detector
+      Description  = "The OpenCV simple blob detection algorithm. See the
+                      documentation at
+                      http://docs.opencv.org/3.1.0/d0/d7a/classcv_1_1SimpleBlo-
+                      bDetector.html"
+      CreatedUsing = detector.Blob@minrepeatability:1
+
+      Group = Parameters
+        BlobColor           = 0
+        FilterByArea        = true
+        FilterByCircularity = false
+        FilterByColor       = true
+        FilterByConvexity   = true
+        FilterByInertia     = true
+        MaxArea             = 5000
+        maxCircularity      = inf
+        MaxConvexity        = inf
+        MaxInertiaRatio     = inf
+        MaxThreshold        = 220
+        MinArea             = 25
+        MinCircularity      = 0.8
+        MinConvexity        = 0.95
+        MinDistance         = 10
+        MinInertiaRatio     = 0.1
+        minrepeatability    = 1
+        MinThreshold        = 50
+        ThresholdStep       = 10
+      End_Group
+    End_Object
+
+    Object = Extractor
+      CVVersion    = 3.1.0
+      Name         = ORB
+      Type         = Feature2D
+      Features     = (Detector, Extractor)
+      Description  = "The OpenCV ORB Feature2D detector/extractor algorithm.
+                      See the documentation at
+                      http://docs.opencv.org/3.1.0/db/d95/classcv_1_1ORB.html"
+      CreatedUsing = orb
+
+      Group = Parameters
+        edgeThreshold = 31
+        fastThreshold = 20
+        firstLevel    = 0
+        nfeatures     = 500
+        nlevels       = 8
+        patchSize     = 31
+        scaleFactor   = 1.2000000476837
+        scoreType     = HARRIS_SCORE
+        WTA_K         = 2
+      End_Group
+    End_Object
+
+    Object = Matcher
+      CVVersion    = 3.1.0
+      Name         = BFMatcher
+      Type         = DecriptorMatcher
+      Features     = Matcher
+      Description  = "The OpenCV BFMatcher DescriptorMatcher matcher
+                      algorithm. See the documentation at
+                      http://docs.opencv.org/3.1.0/d3/da1/classcv_1_1BFMatcher-
+                      .html"
+      CreatedUsing = BFMatcher@NormType:NORM_HAMMING@CrossCheck:false
+
+      Group = Parameters
+        CrossCheck = No
+        NormType   = NORM_HAMMING
+      End_Group
+    End_Object
+
+    Object = Parameters
+      EpiConfidence            = 0.99
+      EpiTolerance             = 3.0
+      FastGeom                 = false
+      FastGeomPoints           = 25
+      Filter                   = None
+      GeomSource               = MATCH
+      GeomType                 = CAMERA
+      HmgTolerance             = 3.0
+      MaxPoints                = 0
+      MinimumFundamentalPoints = 8
+      MinimumHomographyPoints  = 8
+      Ratio                    = 0.65
+      RefineFundamentalMatrix  = true
+      RootSift                 = false
+      SavePath                 = $PWD
+      SaveRenderedImages       = false
+    End_Object
+  End_Object
+End_Object
+End
+                
+

+
+ /dev/tty +
+ + + boolean + Print debugging statements of the matcher algorithm + + 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 + parameters can be made to produce better results. + + false + + + + filename + 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 + very useful to determine, for example, where in the matching and + or outlier detection most of the matches are being rejected. The + output can be lengthy and detailed, but is critical in the + determination where adjustments to the parameters can be made to + provide better results. + + None + *.log + + + + filename + 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. + + None + *.conf + + + + + integer + 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 + 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 + will cause the matching phase and outlier detection to become + costly and inefficient. + + 0 + + +
+ + + + + double + + Specify the maximum distance allowed between ratio test points + + + For each feature point, we have two candidate matches + in the other image. These are the two best ones based on the + distance between their descriptors. If this measured distance is + very low for the best match, and much larger for the second best + match, we can safely accept the first match as a good one since + it is unambiguously the best choice. Reciprocally, if the two + best matches are relatively close in distance, then there exists + a possibility that we make an error if we select one or the + other. In this case, we should reject both matches. Here, we + perform this test by verifying that the ratio of the distance of + the best match over the distance of the second best match is not + greater than a given RATIO threshold. Most of the matches will be + removed by this test. The farther from 1.0, the more matches will + be rejected. + + 0.65 + + + + double + + Specifes tolerance for determining good epipolar points + + + The tolerance specifies the maximum distance in pixels that + feature may deviate from the Epipolar lines for each matching + feature. + + 3.0 + + + + double + + Specifes the level of confidence required in epipolar quality + + + This parameter indicates the confidence level of the epipolar + determination ratio. A value of 1.0 requires that all pixels + be valid in the epipolar computation. + + 0.99 + + + + double + + Specifes tolerance for determining good homography points + + +

+ If we consider the special case where two views of a scene are + separated by a pure rotation, then it can be observed that the + fourth column of the extrinsic matrix will be made of all 0s + (that is, translation is null). As a result, the projective + relation in this special case becomes a 3x3 matrix. This + matrix is called a homography and it implies that, under + special circumstances (here, a pure rotation), the image of a + point in one view is related to the image of the same point in + another by a linear relation. +

+

+ 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 + image. This will throw points out that are (dist > + TOLERANCE * min_dist), the smallest distance between + points. +

+
+ 3.0 +
+ + + integer + + Specify the maximum threads to use + + + 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 + are used if it exceeds the number of CPUs physically available + on the system or no more than MAXTHREADS will be used. + + 0 + +
+ + + + boolean + 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 + 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 + and rotation invariant, this (or something like it) will be required + to orient each images to similar spatial consistency. Users should + determine the capabilities of the matchers used. + + false + + + + integer + + Specify the maximum number of geometry points used to compute the + fast geometry reprojection + + + + 25 + + + + string + 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. + + + + + + + + + + + string + None + Image filtering options for enhanced matching + + Apply an image filter to both images before matching. These filters + are typically used in cases of low emission or incidence angles are + present. They are intended to remove albedo and highlight edges and + are well-suited for these types of feature detectors. + + + + + + + + + + + + + + string + Name of this control network + + The ID or name of this particular control network. This string + will be added to the output control network file, and can be used + to identify the network. + + Features + + + + string + + The pattern to be used to create point ids. + + +

+ This string will be used to create unique IDs for each control + point created by this program. The string must contain a + single series + of question marks ("?"). For example: "VallesMarineris????" +

+

+ The question marks will be replaced + with a number beginning with zero and incremented by one each time + a new control point is created. The example above would cause the + first control point to have an ID of "VallesMarineris0000", the + second ID would be "VallesMarineris0001" and so on. + The maximum number of new control points for this example would be + 10000 with the final ID being "VallesMarineris9999". +

+

+ Note: Make sure there are enough "?"s for all the control + points that might be created during this run. If all the possible + point IDs are exhausted the program will exit with an error, and + will not produce an output control network file. The number of + control points created depends on the size and quantity of + image overlaps and the density of control points as defined + by the DEFFILE parameter. +

+

+ Examples of POINTID: +

+
    +
  • POINTID="JohnDoe?????"
  • +
  • POINTID="Quad1_????"
  • +
  • POINTID="JD_???_test1"
  • +
+
+ FeatureId_????? +
+ + + integer + Start numerical POINTID index with this number + + This parameter can be used to specify the starting POINTID index + number to assist in the creation of unique control point + identifiers. Users must determine the highest used index and use + the next number in the sequence to provide unique point ids. + + 1 + + + + string + + The description of the network. + + + A text description of the contents of the output control network. + The text can contain anything the user wants. For example it + could be used to describe the area of interest the control network + is being made for. + + Find features in image pairs or list + + + + string + Specify type of control network to create + + There are two types of control network files that can be created + in this application: IMAGE and GROUND. For IMAGE types, the pair + is assumed to be overlapping pairs where control measures for + both images are created. For NETTYPE=GROUND, only the FROM file + measure is recorded for purposes of dead reckoning of the image + using jigsaw. + + IMAGE + + + + + + + + string + 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 + that wil provide geometry. + + MATCH + + + + + + + + + + string + + Target parameter for the control network + + + This parameter is optional and not neccessary if using Level 1 ISIS + cube files. It specifies the name of the target body that the input + images are acquired of. If the input images are ISIS images, this + value is retrieved from the camera model or map projection. + + None + + +
+
+ + + + + Run matcher on pair of MESSENGER images + + + This example shows the results of matching two overlapping messenger + images of different scales. The following command was used to + produce the output network: +
+findfeatures algorithm="surf@hessianThreshold:100/surf" \
+             match=EW0211981114G.lev1.cub  \
+             from=EW0242463603G.lev1.cub \
+             epitolerance=1.0 ratio=0.650 hmgtolerance=1.0 \
+             networkid="EW0211981114G_EW0242463603G" \
+             pointid="EW0211981114G_?????" \
+             onet=EW0211981114G.net \
+             description="Test MESSENGER pair" debug=true \
+             debuglog=EW0211981114G.log
+        
+

+ Note that the fast geom option is not used for this example because + the SURF algorithm is scale and rotation invariant. Here is the + algorithm information for the specification of the matcher + parameters: +

+ + findfeatures algorithm="surf@hessianThreshold:100/surf" listspec=true + +

+
+Object = FeatureAlgorithms
+  Object = RobustMatcher
+    OpenCVVersion = 3.1.0
+    Name          = surf@hessianThreshold:100/surf/BFMatcher
+
+    Object = Detector
+      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_1xfeatures-
+                      2d_1_1SURF.html"
+      CreatedUsing = surf@hessianThreshold:100
+
+      Group = Parameters
+        Extended         = No
+        hessianThreshold = 100
+        NOctaveLayers    = 3
+        NOctaves         = 4
+        Upright          = No
+      End_Group
+    End_Object
+
+    Object = Extractor
+      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_1xfeatures-
+                      2d_1_1SURF.html"
+      CreatedUsing = surf
+
+      Group = Parameters
+        Extended         = No
+        HessianThreshold = 100.0
+        NOctaveLayers    = 3
+        NOctaves         = 4
+        Upright          = No
+      End_Group
+    End_Object
+
+    Object = Matcher
+      CVVersion    = 3.1.0
+      Name         = BFMatcher
+      Type         = DecriptorMatcher
+      Features     = Matcher
+      Description  = "The OpenCV BFMatcher DescriptorMatcher matcher
+                      algorithm. See the documentation at
+                      http://docs.opencv.org/3.1.0/d3/da1/classcv_1_1BFMatcher-
+                      .html"
+      CreatedUsing = BFMatcher
+
+      Group = Parameters
+        CrossCheck = No
+        NormType   = NORM_L2
+      End_Group
+    End_Object
+
+    Object = Parameters
+      EpiConfidence            = 0.99
+      EpiTolerance             = 3.0
+      FastGeom                 = false
+      FastGeomPoints           = 25
+      Filter                   = None
+      GeomSource               = MATCH
+      GeomType                 = CAMERA
+      HmgTolerance             = 3.0
+      MaxPoints                = 0
+      MinimumFundamentalPoints = 8
+      MinimumHomographyPoints  = 8
+      Ratio                    = 0.65
+      RefineFundamentalMatrix  = true
+      RootSift                 = false
+      SavePath                 = $PWD
+      SaveRenderedImages       = false
+    End_Object
+  End_Object
+End_Object
+End
+        
+

+ The output debug log file and a line-by-line description of the + 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 + + +This provides a reference for all the current algorithms and their default parameters. This list may not include all the available OpenCV algorithms nor may all algorithms be applicable. Users should rerun this command to get the current options available on your system as they may differ. +

+ + findfeatures listall=yes + +

+
+Object = Algorithms
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = AGAST
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV AGAST Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d7/d19/classcv_1_1AgastFeatur-
+                    eDetector.html"
+    CreatedUsing = agast
+    Aliases      = (agast, detector.agast)
+
+    Group = Parameters
+      NonmaxSuppression = Yes
+      Threshold         = 10
+      Type              = OAST_9_16
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = AKAZE
+    Type         = Feature2D
+    Features     = (Detector, Extractor)
+    Description  = "The OpenCV AKAZE Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d8/d30/classcv_1_1AKAZE.html"
+    CreatedUsing = akaze
+    Aliases      = (akaze, detector.akaze, extractor.akaze, feature2d.akaze)
+
+    Group = Parameters
+      DescriptorChannels = 3
+      DescriptorSize     = 0
+      DescriptorType     = DESCRIPTOR_MLDB
+      Diffusivity        = DIFF_PM_G2
+      NOctaveLayers      = 4
+      NOctaves           = 4
+      Threshold          = 0.0010000000474975
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = Blob
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV simple blob detection algorithm. See the
+                    documentation at
+                    http://docs.opencv.org/3.1.0/d0/d7a/classcv_1_1SimpleBlobD-
+                    etector.html"
+    CreatedUsing = blob
+    Aliases      = (blob, detector.blob)
+
+    Group = Parameters
+      BlobColor           = 0
+      FilterByArea        = true
+      FilterByCircularity = false
+      FilterByColor       = true
+      FilterByConvexity   = true
+      FilterByInertia     = true
+      MaxArea             = 5000
+      maxCircularity      = inf
+      MaxConvexity        = inf
+      MaxInertiaRatio     = inf
+      MaxThreshold        = 220
+      MinArea             = 25
+      MinCircularity      = 0.8
+      MinConvexity        = 0.95
+      MinDistance         = 10
+      MinInertiaRatio     = 0.1
+      MinRepeatability    = 2
+      MinThreshold        = 50
+      ThresholdStep       = 10
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = BRISK
+    Type         = Feature2D
+    Features     = (Detector, Extractor)
+    Description  = "The OpenCV BRISK Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/de/dbf/classcv_1_1BRISK.html"
+    CreatedUsing = brisk
+    Aliases      = (brisk, detector.brisk, extractor.brisk, feature2d.brisk)
+
+    Group = Parameters
+      NOctaves     = 3
+      PatternScale = 1.0
+      Threshold    = 30
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = FAST
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV FAST Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/df/d74/classcv_1_1FASTFeature-
+                    Detector.html"
+    CreatedUsing = fast
+    Aliases      = (detector.fast, fast, fastx)
+
+    Group = Parameters
+      NonmaxSuppression = Yes
+      Threshold         = 10
+      Type              = TYPE_9_16
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = GFTT
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV GFTT Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/df/d21/classcv_1_1GFTTDetecto-
+                    r.html"
+    CreatedUsing = gftt
+    Aliases      = (detector.gftt, gftt)
+
+    Group = Parameters
+      BlockSize      = 3
+      HarrisDetector = No
+      K              = 0.04
+      MaxFeatures    = 1000
+      MinDistance    = 1.0
+      QualityLevel   = 0.01
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = KAZE
+    Type         = Feature2D
+    Features     = (Detector, Extractor)
+    Description  = "The OpenCV KAZE Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d3/d61/classcv_1_1KAZE.html"
+    CreatedUsing = kaze
+    Aliases      = (detector.kaze, extractor.kaze, feature2d.kaze, kaze)
+
+    Group = Parameters
+      Diffusivity   = DIFF_PM_G2
+      Extended      = No
+      NOctaveLayers = 4
+      NOctaves      = 4
+      Threshold     = 0.0010000000474975
+      Upright       = No
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = MSD
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV MSD Feature2D detector/extractor algorithm. See
+                    the documentation at
+                    http://docs.opencv.org/3.1.0/d6/d36/classcv_1_1xfeatures2d-
+                    _1_1MSD.html"
+    CreatedUsing = msd
+    Aliases      = (detector.msd, msd)
+
+    Group = Parameters
+      ComputeOrientation = false
+      KNN                = 4
+      NMSRadius          = 5
+      NMSScaleRadius     = 0
+      NScales            = -1
+      PatchRadius        = 3
+      ScaleFactor        = 1.25
+      SearchAreaRadius   = 5
+      THSaliency         = 250.0
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = MSER
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV MSER Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d3/d28/classcv_1_1MSER.html"
+    CreatedUsing = mser
+    Aliases      = (detector.mser, mser)
+
+    Group = Parameters
+      AreaThreshold = 1.01
+      Delta         = 5
+      EdgeBlurSize  = 5
+      MaxArea       = 14400
+      MaxEvolution  = 200
+      MaxVariation  = 0.25
+      MinArea       = 60
+      MinDiversity  = 0.2
+      MinMargin     = 0.003
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = ORB
+    Type         = Feature2D
+    Features     = (Detector, Extractor)
+    Description  = "The OpenCV ORB Feature2D detector/extractor algorithm. See
+                    the documentation at
+                    http://docs.opencv.org/3.1.0/db/d95/classcv_1_1ORB.html"
+    CreatedUsing = orb
+    Aliases      = (detector.orb, extractor.orb, feature2d.orb, orb)
+
+    Group = Parameters
+      edgeThreshold = 31
+      fastThreshold = 20
+      firstLevel    = 0
+      nfeatures     = 500
+      nlevels       = 8
+      patchSize     = 31
+      scaleFactor   = 1.2000000476837
+      scoreType     = HARRIS_SCORE
+      WTA_K         = 2
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = SIFT
+    Type         = Feature2D
+    Features     = (Detector, Extractor)
+    Description  = "The OpenCV SIFT Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d5/d3c/classcv_1_1xfeatures2d-
+                    _1_1SIFT.html"
+    CreatedUsing = sift
+    Aliases      = (detector.sift, extractor.sift, feature2d.sift, sift)
+
+    Group = Parameters
+      constrastThreshold = 0.04
+      edgeThreshold      = 10
+      nfeatures          = 0
+      nOctaveLayers      = 3
+      sigma              = 1.6
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = Star
+    Type         = Feature2D
+    Features     = Detector
+    Description  = "The OpenCV Star Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d6/d36/classcv_1_1xfeatures2d-
+                    _1_1Star.html"
+    CreatedUsing = star
+    Aliases      = (detector.star, star)
+
+    Group = Parameters
+      LineThresholdBinarized = 8
+      LineThresholdProjected = 10
+      MaxSize                = 45
+      ResponseThreshold      = 30
+      SuppressNonmaxSize     = 5
+    End_Group
+  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
+    Name         = Brief
+    Type         = Feature2D
+    Features     = Extractor
+    Description  = "The OpenCV simple blob detection algorithm. See the
+                    documentation at
+                    http://docs.opencv.org/3.1.0/d0/d7a/classcv_1_1SimpleBlobD-
+                    etector.html"
+    CreatedUsing = brief
+    Aliases      = (brief, extractor.brief)
+
+    Group = Parameters
+      Bytes          = 32
+      UseOrientation = true
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = DAISY
+    Type         = Feature2D
+    Features     = Extractor
+    Description  = "The OpenCV DAISY Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d9/d37/classcv_1_1xfeatures2d-
+                    _1_1DAISY.html"
+    CreatedUsing = daisy
+    Aliases      = (daisy, extractor.daisy)
+
+    Group = Parameters
+      H               = "1,0,0,0,1,0,0,0,1"
+      interpolation   = true
+      norm            = NRM_NONE
+      q_hist          = 8
+      q_radius        = 3
+      q_theta         = 8
+      radius          = 15
+      use_orientation = false
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = FREAK
+    Type         = Feature2D
+    Features     = Extractor
+    Description  = "The OpenCV FREAK Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/df/db4/classcv_1_1xfeatures2d-
+                    _1_1FREAK.html"
+    CreatedUsing = freak
+    Aliases      = (extractor.freak, freak)
+
+    Group = Parameters
+      NOctaves              = 4
+      OrientationNormalized = true
+      PatternScale          = 22.0
+      ScaleNormalized       = true
+      SelectedPairs         = Null
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = LATCH
+    Type         = Feature2D
+    Features     = Extractor
+    Description  = "The OpenCV LATCH Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d6/d36/classcv_1_1xfeatures2d-
+                    _1_1LATCH.html"
+    CreatedUsing = latch
+    Aliases      = (extractor.latch, latch)
+
+    Group = Parameters
+      Bytes              = 32
+      HalfSSDSize        = 3
+      RotationInvariance = true
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = LUCID
+    Type         = Feature2D
+    Features     = Extractor
+    Description  = "The OpenCV LUCID Feature2D detector/extractor algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d6/d36/classcv_1_1xfeatures2d-
+                    _1_1LUCID.html"
+    CreatedUsing = lucid
+    Aliases      = (extractor.lucid, lucid)
+
+    Group = Parameters
+      BlurKernel  = 1
+      LucidKernel = 1
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = BFMatcher
+    Type         = DecriptorMatcher
+    Features     = Matcher
+    Description  = "The OpenCV BFMatcher DescriptorMatcher matcher algorithm.
+                    See the documentation at
+                    http://docs.opencv.org/3.1.0/d3/da1/classcv_1_1BFMatcher.h-
+                    tml"
+    CreatedUsing = bfmatcher
+    Aliases      = (bfmatcher, matcher.bfmatcher)
+
+    Group = Parameters
+      CrossCheck = No
+      NormType   = NORM_L2
+    End_Group
+  End_Object
+
+  Object = Algorithm
+    CVVersion    = 3.1.0
+    Name         = FlannBasedMatcher
+    Type         = DecriptorMatcher
+    Features     = Matcher
+    Description  = "The OpenCV FlannBasedMatcher DescriptorMatcher matcher
+                    algorithm. See the documentation at
+                    http://docs.opencv.org/3.1.0/dc/de2/classcv_1_1FlannBasedM-
+                    atcher.html"
+    CreatedUsing = flannbasedmatcher
+    Aliases      = (flannbasedmatcher, matcher.flannbasedmatcher)
+
+    Group = Parameters
+      Checks  = 32
+      Epsilon = 0.0
+      Sorted  = Yes
+    End_Group
+  End_Object
+End_Object
+End
+	
+
+
+
+ +
diff --git a/isis/python_bindings/astroset.i b/isis/python_bindings/astroset.i new file mode 100644 index 0000000000000000000000000000000000000000..34e969a708e7007b5da8e768ff2d55a2480f55ea --- /dev/null +++ b/isis/python_bindings/astroset.i @@ -0,0 +1,155 @@ +%module astroset + +%pythoncode %{ +from . import apps +%} + +%include "std_vector.i" +%include "std_string.i" +%include "std_array.i" +%include "std_map.i" +%include "carrays.i" +%include "std_pair.i" + +#include + +%{ + #include + #include + #include +%} + +%typemap(in) nlohmann::json { + if (PyDict_Check($input) || PyList_Check($input)) { + PyObject* module = PyImport_ImportModule("json"); + PyObject* jsonDumps = PyUnicode_FromString("dumps"); + PyObject* pythonJsonString = PyObject_CallMethodObjArgs(module, jsonDumps, $input, NULL); + $1 = nlohmann::json::parse(PyUnicode_AsUTF8(pythonJsonString)); + } + else { + PyErr_SetString(PyExc_TypeError, "not a json serializable type"); + SWIG_fail; + } +} + +%typemap(typecheck, precedence=SWIG_TYPECHECK_MAP) nlohmann::json { + $1 = PyDict_Check($input) ? 1 : 0; +} + +%typemap(out) nlohmann::json { + PyObject* module = PyImport_ImportModule("json"); + PyObject* jsonLoads = PyUnicode_FromString("loads"); + std::string jsonString = ($1).dump(); + PyObject* pythonJsonString = PyUnicode_DecodeUTF8(jsonString.c_str(), jsonString.size(), NULL); + $result = PyObject_CallMethodObjArgs(module, jsonLoads, pythonJsonString, NULL); +} + +#include +#include +%typemap(in) QVector & { + int i; + if (!PySequence_Check($input)) { + PyErr_SetString(PyExc_ValueError,"Expected a List"); + SWIG_fail; + } + + int size = PySequence_Length($input); + $1 = new QVector(size); + + for (i = 0; i < size; i++) { + PyObject *o = PySequence_GetItem($input,i); + if (PyUnicode_Check(o)) { + QString s = QString::fromUtf8(PyUnicode_AsUTF8(o)); + (*$1)[i] = s; + } else { + PyErr_SetString(PyExc_ValueError,"Sequence elements must be string"); + SWIG_fail; + } + } +} + + + +%typemap(out) QVector & { + std::cout << "returning stuff" << std::endl; + $result = PyList_New($1_dim0); + for (i = 0; i < $1_dim0; i++) { + PyObject *o = PyString_AsString($1->at(i)); + PyList_SetItem($result,i,o); + } +} + + +%typemap(in) QString const & { + if (!PyUnicode_Check($input)) { + PyErr_SetString(PyExc_ValueError,"Expected a String"); + SWIG_fail; + } + $1 = new QString(QString::fromUtf8(PyUnicode_AsUTF8($input))); +} + + +%typemap(out) QString const & { + $result = Py_BuildValue("s#", $1.toStdString().c_str(), $1.size()); +} + + +%typemap(in) QString { + if (!PyUnicode_Check($input)) { + PyErr_SetString(PyExc_ValueError,"Expected a String"); + SWIG_fail; + } + $1 = QString::fromUtf8(PyUnicode_AsUTF8($input)); +} + + +%typemap(out) QString = QString const &; + + +%typemap(in) QVector { + int i; + if (!PySequence_Check($input)) { + PyErr_SetString(PyExc_ValueError,"Expected a List"); + SWIG_fail; + } + + int size = PySequence_Length($input); + $1 = new QVector(size); + + for (i = 0; i < size; i++) { + PyObject *o = PySequence_GetItem($input,i); + if (PyUnicode_Check(o)) { + QString s = QString::fromUtf8(PyUnicode_AsUTF8(o)); + (*$1)[i] = s; + } else { + PyErr_SetString(PyExc_ValueError,"Sequence elements must be string"); + SWIG_fail; + } + } +} + + +%typemap(out) QVector = QVector &; + +namespace std { + %template(IntVector) vector; + %template(DoubleVector) vector; + %template(VectorDoubleVector) vector< vector >; + %template(StringVector) vector; + %template(ConstCharVector) vector; + %template(PairDoubleVector) vector>; + %template(DoubleArray6) array; +} + +%exception { + try { + $action + } catch (std::exception const& e) { + SWIG_exception(SWIG_RuntimeError, (std::string("std::exception: ") + e.what()).c_str()); + } catch (...) { + SWIG_exception(SWIG_UnknownError, "Unknown error"); + } +} + + +%include "UserInterface.i" diff --git a/isis/python_bindings/setup.py.in b/isis/python_bindings/setup.py.in new file mode 100644 index 0000000000000000000000000000000000000000..97cd6289d487bd74ec9fe9654fb4c7eb072d3406 --- /dev/null +++ b/isis/python_bindings/setup.py.in @@ -0,0 +1,14 @@ +import setuptools + +from setuptools import find_packages + +if __name__ == '__main__': + setuptools.setup( + name='astroset', + version='${CMAKE_PROJECT_VERSION}', + packages=find_packages(), + package_data={'':['_astroset.so']}, + scripts=["astroset/apps/findFeaturesSegment/findFeaturesSegment.py"], + zip_safe=False, # Some OSes can not dynamically load an so from an egg (zipfile) + license='CC0-1.0', +) \ No newline at end of file diff --git a/isis/python_bindings/tests/__pycache__/test_install.cpython-39-pytest-7.4.0.pyc b/isis/python_bindings/tests/__pycache__/test_install.cpython-39-pytest-7.4.0.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54858f6a36802e5b061c5ce47adabb934f13528d Binary files /dev/null and b/isis/python_bindings/tests/__pycache__/test_install.cpython-39-pytest-7.4.0.pyc differ diff --git a/isis/python_bindings/tests/test_install.py b/isis/python_bindings/tests/test_install.py new file mode 100644 index 0000000000000000000000000000000000000000..999cbd9c46365663a273519902751db6a50859d8 --- /dev/null +++ b/isis/python_bindings/tests/test_install.py @@ -0,0 +1,38 @@ +import importlib.util +import sys +import subprocess + + +def test_module_import(): + name = 'astroset' + if name in sys.modules: + print(f"{name!r} already in sys.modules") + assert (spec := importlib.util.find_spec(name)) is not None + +def test_check_pip_install(): + package_name = "astroset" + try: + # Check if the package is installed by running the "pip show" command + result = subprocess.check_output(['pip', 'show', package_name]) + package_info = result.decode('utf-8') + + # Parse the package information to check if it contains the package name + assert(package_name.lower() in package_info.lower()) + + except subprocess.CalledProcessError: + assert False + +def test_app_install(): + app_name = 'findFeaturesSegment.py' + try: + # Check if the package is installed by running the "pip show" command + result = subprocess.check_output([app_name, '-H']) + help_string = result.decode('utf-8') + + # Parse the package information to check if it contains the package name + assert("from" in help_string.lower()) + assert("fromlist" in help_string.lower()) + + except subprocess.CalledProcessError: + assert False + \ No newline at end of file diff --git a/isis/src/CMakeLists.txt b/isis/src/CMakeLists.txt index 78f41d620bc0ce0526ac04c2d25b3a0851d78da0..998a0bdb640689ce2213d358fac6ae0e756bf420 100644 --- a/isis/src/CMakeLists.txt +++ b/isis/src/CMakeLists.txt @@ -16,3 +16,4 @@ foreach( mod ${modules} ) add_isis_module(${strippedmod} ${strippedmod}) endforeach() + diff --git a/isis/src/base/apps/segment/segment.cpp b/isis/src/base/apps/segment/segment.cpp index 5c95b1499d8300628127c35470199fce690c3c8d..7cb1bfde99bb1abc56e888d54fb960ed56d95f14 100644 --- a/isis/src/base/apps/segment/segment.cpp +++ b/isis/src/base/apps/segment/segment.cpp @@ -43,6 +43,11 @@ namespace Isis { else { parameters += toString(numberOfLines); } + + if(ui.GetParamPreference() != "") { + parameters += " -PREF=" + ui.GetParamPreference(); + } + ProgramLauncher::RunIsisProgram("crop", parameters); //The starting line for next crop startLine = 1 + cropNum * (numberOfLines - lineOverlap); diff --git a/isis/src/base/objs/IsisAml/IsisAml.cpp b/isis/src/base/objs/IsisAml/IsisAml.cpp index 4cd9d0bf0f37464e2e528a10d84f513969d3c70c..cf7cf1cec14725cb0ea0ed0586e02db8b8f518b8 100644 --- a/isis/src/base/objs/IsisAml/IsisAml.cpp +++ b/isis/src/base/objs/IsisAml/IsisAml.cpp @@ -10,6 +10,9 @@ find files of those names at the top level of this repository. **/ #include #include +#include +using json = nlohmann::json; + #include "FileName.h" #include "IException.h" #include "IsisAml.h" @@ -638,7 +641,6 @@ void IsisAml::GetAsString(const QString ¶mName, QString IsisAml::GetFileName(const QString ¶mName, QString extension) const { const IsisParameterData *param = ReturnParam(paramName); - if(param->type != "filename") { QString message = "Parameter [" + paramName + "] is not a filename."; throw Isis::IException(Isis::IException::Programmer, message, _FILEINFO_); @@ -1196,6 +1198,65 @@ int IsisAml::GroupIndex(const QString & grpName) const { } +/** + * Creates a json object which could be used as a command line + * + */ +json IsisAml::GetParams() { + json ret = {}; + Isis::PvlGroup group("UserParameters"); + + // Add appropriate keywords + for(unsigned int g = 0; g < groups.size(); g++) { + for(unsigned int p = 0; p < groups[g].parameters.size(); p++) { + const IsisParameterData *param = ReturnParam(ParamName(g, p)); + // If this param has a value add it to the command line + if(param->values.size() > 0) { + std::vector values(param->values.size()); + + for(unsigned int value = 0; value < param->values.size(); value++) { + values[value] = param->values[value].toStdString(); + } + + if (values.size() == 0) { + ret[param->name.toStdString()] = "NULL"; + } + if (values.size() > 1) { + ret[param->name.toStdString()] = values; + } + else { + ret[param->name.toStdString()] = values.at(0); + } + + } + + // Or if it has a default value add it to the command line + else if(param->defaultValues.size() > 0) { + std::vector values(param->defaultValues.size()); + + for(unsigned int value = 0; + value < param->defaultValues.size(); + value++) { + values[value] = param->defaultValues[value].toStdString(); + } + + if (values.size() == 0) { + ret[param->name.toStdString()] = "NULL"; + } + else if (values.size() > 1) { + ret[param->name.toStdString()] = values; + } + else { + ret[param->name.toStdString()] = values.at(0); + } + } + } + } + + return ret; +} + + /** * Create a PVL file from the parameters in a Group given the Gui group name, * Pvl Object and Group names and the list of parameters to be included in the diff --git a/isis/src/base/objs/IsisAml/IsisAml.h b/isis/src/base/objs/IsisAml/IsisAml.h index 7b46d45b2b6ddf020996516002de964b5a8aa4cb..8dc4ab4b0d9a464119ca351687c0ef58c72664e2 100644 --- a/isis/src/base/objs/IsisAml/IsisAml.h +++ b/isis/src/base/objs/IsisAml/IsisAml.h @@ -15,6 +15,8 @@ find files of those names at the top level of this repository. **/ #include +#include + #include "IsisXMLApplication.h" #include "IsisAmlData.h" #include "Pvl.h" @@ -210,6 +212,8 @@ class IsisAml : protected IsisAmlData { //! Given the group name get its index in group array int GroupIndex(const QString & grpName) const; + + nlohmann::json GetParams(); int NumParams(const int &) const; diff --git a/isis/src/base/objs/Preference/Preference.h b/isis/src/base/objs/Preference/Preference.h index 2a0e59f6ae97c37c60b6ead075fa5366ca3b81ea..ce102a6fa30cff2a6aa1e5361262be8bc77081fa 100644 --- a/isis/src/base/objs/Preference/Preference.h +++ b/isis/src/base/objs/Preference/Preference.h @@ -85,7 +85,6 @@ namespace Isis { static Preference *p_preference; //!< Pointer to a Preference object static bool p_unitTest; /**< Flag indicating whether the file is a unitTest or not.*/ - static void Shutdown(); }; }; diff --git a/isis/src/base/objs/UserInterface/UserInterface.cpp b/isis/src/base/objs/UserInterface/UserInterface.cpp index fcea3b1c10e8a8c7dc05910fc5148383005fd380..bc22e5ac895924546f064cf6250d8a4067482638 100644 --- a/isis/src/base/objs/UserInterface/UserInterface.cpp +++ b/isis/src/base/objs/UserInterface/UserInterface.cpp @@ -101,6 +101,7 @@ namespace Isis { } } + //! Destroys the UserInterface object UserInterface::~UserInterface() { // can't unit test - p_gui will be NULL in unit test @@ -483,6 +484,8 @@ namespace Isis { // resolve the reserved parameter (e.g. set -h to -HELP) paramName = resolveParameter(paramName, options); + + // Prevent double handling of -LAST to prevent conflicts // Keep track of using -LAST to prevent conflicts with -BATCHLIST @@ -914,6 +917,7 @@ namespace Isis { } else if (name == "-PREFERENCE") { p.Load(value); + p_preference = value; } else if (name == "-LOG") { if( value.isEmpty() ) { @@ -937,6 +941,7 @@ namespace Isis { } + /** * This gets the next parameter in the list of arguments. curPos will be changed * to be the end of the current argument (still needs incremented to get the diff --git a/isis/src/base/objs/UserInterface/UserInterface.h b/isis/src/base/objs/UserInterface/UserInterface.h index 34ced71c99f6068560ce23e96ff3b9034f00d465..9f33680fe66c9b93a256d1956110b6ad8aa934c5 100644 --- a/isis/src/base/objs/UserInterface/UserInterface.h +++ b/isis/src/base/objs/UserInterface/UserInterface.h @@ -6,7 +6,6 @@ For more details about the LICENSE terms and the AUTHORS, you will find files of those names at the top level of this repository. **/ /* SPDX-License-Identifier: CC0-1.0 */ -#include "FileName.h" #include "Gui.h" #include "IsisAml.h" #include "PvlTokenizer.h" @@ -188,6 +187,15 @@ namespace Isis { return p_gui; }; + /** + * @brief Get the the preference path passed in by the user. Otherwise an empty string + * + * @return QString + */ + QString GetParamPreference() { + return p_preference; + } + QString GetInfoFileName(); bool GetInfoFlag(); @@ -238,6 +246,7 @@ namespace Isis { QString p_progName; //! FileName to save last history to. QString p_saveFile; + QString p_preference; }; };