Repository: tum-vision/online_photometric_calibration Branch: master Commit: 3ede3f960ad0 Files: 33 Total size: 366.0 KB Directory structure: gitextract_g6ru51pt/ ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── AUTHORS.txt ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE.txt ├── README.md └── src/ ├── CLI11.hpp ├── Database.cpp ├── Database.h ├── Feature.h ├── Frame.h ├── GainRobustTracker.cpp ├── GainRobustTracker.h ├── ImageReader.cpp ├── ImageReader.h ├── JacobianGenerator.cpp ├── JacobianGenerator.h ├── NonlinearOptimizer.cpp ├── NonlinearOptimizer.h ├── OptimizationBlock.cpp ├── OptimizationBlock.h ├── OptimizedPoint.h ├── RapidExposureTimeEstimator.cpp ├── RapidExposureTimeEstimator.h ├── ResponseModel.h ├── StandardIncludes.h ├── Tracker.cpp ├── Tracker.h ├── VignetteModel.cpp ├── VignetteModel.h └── main.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # IDE files .idea/* # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # Ignored by Rui CMakeLists.txt.user* build/ */build/ */bin/ */lib/ *~ cmake-build-*/ ================================================ FILE: .gitlab-ci.yml ================================================ # defaults image: nikolausdemmel/ubuntu-dev-cv:16.04 variables: BUILD_TYPE: Release # template definition .compile_template: &compile_definition stage: build before_script: - mkdir -p ccache - export CCACHE_BASEDIR=${PWD} - export CCACHE_DIR=${PWD}/ccache cache: paths: - ccache/ script: - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} - make -j3 release-16.04: <<: *compile_definition debug-16.04: variables: BUILD_TYPE: Debug <<: *compile_definition clang-16.04: variables: CC: /usr/bin/clang-5.0 CXX: /usr/bin/clang++-5.0 <<: *compile_definition release-14.04: image: nikolausdemmel/ubuntu-dev-cv:14.04 variables: CC: /usr/lib/ccache/gcc CXX: /usr/lib/ccache/g++ <<: *compile_definition ================================================ FILE: .travis.yml ================================================ sudo: required language: cpp services: - docker cache: ccache env: global: - DOCKER_CONTAINER_NAME=nikolausdemmel/ubuntu-dev-cv-gui - CC=/usr/bin/gcc - CXX=/usr/bin/g++ - BUILD_TYPE=Release matrix: include: - os: linux env: DIST_VERS=16.04 - os: linux env: DIST_VERS=16.04 CC=/usr/bin/clang-5.0 CXX=/usr/bin/clang++-5.0 - os: linux env: DIST_VERS=16.04 BUILD_TYPE=Debug - os: linux env: DIST_VERS=14.04 - os: osx before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi install: | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker pull $DOCKER_CONTAINER_NAME:$DIST_VERS fi if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then # upgrade python first, else linking python@2 fails (dependency of opencv@2) brew upgrade python # removing pip numpy, else installing brew numpy fails (dependency of opencv@2) brew install python@2 /usr/bin/yes | pip uninstall numpy # upgrade already installed brew upgrade cmake # install additional dependencies brew install opencv@2 ccache fi script: | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run --name ci -it \ -v ${TRAVIS_BUILD_DIR}:/repo \ -v $HOME/.ccache:/root/.ccache \ -e BUILD_TYPE -e CC -e CXX \ $DOCKER_CONTAINER_NAME:$DIST_VERS \ /bin/bash -c ' export PS4='\''+ \e[33;1m($0 @ line $LINENO) \$\e[0m '\'' # quotes must be escaped set -e # exit on failure set -x # trace for debug mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE /repo cmake --build .' fi if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. cmake --build . fi # TODO: try w/o docker, just installing from apt; compare speed. #jobs: # foo: ================================================ FILE: AUTHORS.txt ================================================ Authors ordered by first contribution. Paul Bergmann Rui Wang Nikolaus Demmel ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [0.1.0] - 2018-05-22 ### Added - Initial release ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2) set(PROJECT_NAME online_photometric_calibration) project(${PROJECT_NAME}) # Set default build type if not specified otherwise. # See https://cmake.org/pipermail/cmake/2012-May/050243.html if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) message(STATUS "Setting build type to '${CMAKE_BUILD_TYPE}' as none was specified.") # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() # We need at least C++11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # set march=native unless specified if(NOT CXX_MARCH) set(CXX_MARCH native) endif() SET(CMAKE_CXX_FLAGS_RELEASE "-march=${CXX_MARCH} ${CMAKE_CXX_FLAGS_RELEASE}") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-march=${CXX_MARCH} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") # warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-sign-compare") # Required libraries if(APPLE) # give the homebrew path as hint, since the formula is keg-only find_package(OpenCV 2.4 REQUIRED HINTS /usr/local/opt/opencv@2) else() find_package(OpenCV 2.4 REQUIRED) endif() find_package(Threads REQUIRED) # Configure CCache if available (requires cmake 3.4 or newer) find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) message(STATUS "Found ccache: ${CCACHE_PROGRAM}") set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) endif() # output paths in the build directory set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # include path include_directories(${PROJECT_SOURCE_DIR}/src) # Source files set(ONLINE_CALIB_SOURCE_FILES src/Database.cpp src/GainRobustTracker.cpp src/ImageReader.cpp src/JacobianGenerator.cpp src/NonlinearOptimizer.cpp src/OptimizationBlock.cpp src/RapidExposureTimeEstimator.cpp src/Tracker.cpp src/VignetteModel.cpp ) # add include files to custom target so they show up in IDEs like # QtCreator in the project view file(GLOB_RECURSE _INCLUDE_FILES "src/*.h" "src/*.hpp") add_custom_target(_include_files_fix_target SOURCES ${_INCLUDE_FILES}) # main library add_library(online_pcalib SHARED ${ONLINE_CALIB_SOURCE_FILES}) target_link_libraries(online_pcalib ${OpenCV_LIBS} ${CMAKE_THREAD_LIBS_INIT}) # demo executable for online calibration add_executable(online_pcalib_demo src/main.cpp) target_link_libraries(online_pcalib_demo online_pcalib) # install rules install (TARGETS online_pcalib online_pcalib_demo ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) ================================================ FILE: LICENSE.txt ================================================ 3-Clause BSD License Copyright 2017-2018 Paul Bergmann and co-authors (see AUTHORS.txt) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # Online Photometric Calibration Recent direct visual odometry and SLAM algorithms have demonstrated impressive levels of precision. However, they require a photometric camera calibration in order to achieve competitive results. Hence, the respective algorithm cannot be directly applied to an off-the-shelf-camera or to a video sequence acquired with an unknown camera. In this work we propose a method for online photometric calibration which enables to process auto exposure videos with visual odometry precisions that are on par with those of photometrically calibrated videos. Our algorithm recovers the exposure times of consecutive frames, the camera response function, and the attenuation factors of the sensor irradiance due to vignetting. Gain robust KLT feature tracks are used to obtain scene point correspondences as input to a nonlinear optimization framework. We show that our approach can reliably calibrate arbitrary video sequences by evaluating it on datasets for which full photometric ground truth is available. We further show that our calibration can improve the performance of a state-of-the-art direct visual odometry method that works solely on pixel intensities, calibrating for photometric parameters in an online fashion in realtime. For more details please refer to our paper. If you use this code in your research, we would appreciate if you cite the respective publication. **Online Photometric Calibration of Auto Exposure Video for Realtime Visual Odometry and SLAM** (P. Bergmann, R. Wang, D. Cremers), *In IEEE Robotics and Automation Letters (RA-L)*, volume 3, 2018. [[pdf](https://vision.in.tum.de/_media/spezial/bib/bergmann17calibration.pdf)] [[video](https://youtu.be/nQHMG0c6Iew)] For more information on photometric calibration, see https://vision.in.tum.de/research/vslam/photometric-calibration. **Note:** *This is a preliminary release. You should consider this research code in beta. All interfaces are subject to change.* ## Install We support Ubuntu 14.04 and 16.04, and macOS, but it might work on a variety of other platforms that meet the dependency requirements. ### Dependencies The main dependencies are cmake 3.2 or later, a C++11 compiler, and OpenCV 2.4. #### Ubuntu 14.04 On Ubuntu 14.04 you need to get a more recent version of cmake. ``` sudo add-apt-repository ppa:george-edison55/cmake-3.x ``` Now continue to install dependencies like for Ubuntu 16.04. #### Ubuntu 16.04 **Required:** ``` sudo apt-get update sudo apt-get install \ build-essential \ g++ \ cmake \ libopencv-dev ``` **Optional:** CCache can help you speed up repeated builds. *Note:* You need at least cmake version 3.4 for ccache to work automatically. ``` sudo apt-get install ccache ``` ### macOS We assume you have installed [Homebrew](https://brew.sh). **Required:** ``` brew install cmake opencv@2 ``` **Optional:** ``` brew install ccache ``` ### Compile Start in the package directory. ``` mkdir build cd build cmake .. make -j4 ``` Optionally you can install the built libraries and executables. ``` sudo make install ``` ## Usage ### Online calibration **Example usage:** Download sequence of the TUMmono VO dataset. ``` SEQ=30 wget http://vision.in.tum.de/mono/dataset/sequence_$SEQ.zip unzip sequence_$SEQ.zip unzip -d sequence_$SEQ/images sequence_$SEQ/images.zip ``` Run online calibration. ``` build/bin/online_pcalib_demo -i sequence_$SEQ/images --exposure-gt-file sequence_$SEQ/times.txt ``` *Note:* Currently the implementation is not suitable for fisheye-lenses with black borders around the image, which includes some of the TUMmono VO dataset sequences as well as the TUM VI dataset sequences. ### Batch calibration Online calibration runs the code in a multithreaded way in parallel on the CPU. If tracking and backend optimization should be performed sequentially and real time performance is not required, the system can be run in batch calibration mode. For running in batch mode, simply add the command line option ``` --calibration-mode batch ``` For batch calibration you might want to use the exposure times from the optimization backend rather than the rapidly estimated exposure times from the frontend. In order to extract more keyframes to the backend optimizer, the run_settings parameters have to be adjusted. These parameters can be changed by manually setting: ``` --nr-active-frames INT Maximum number of frames to be stored in the database. --keyframe-spacing INT Number of frames that keyframes are apart in the backend optimizer. --min-keyframes-valid INT Minimum number of frames a feature has to be tracked to be considered for optimization. ``` ### Command line options ``` Photometric Calibration Usage: online_pcalib_demo [OPTIONS] Options: -h,--help Print this help message and exit -i,--image-folder TEXT=images Folder with image files to read. --start-image-index INT=0 Start reading from this image index. --end-image-index INT=-1 Stop reading at this image index. --image-width INT=640 Resize image to this width. --image-height INT=480 Resize image to this height. --exposure-gt-file TEXT=times.txt Textfile containing ground truth exposure times for each frame for visualization. --calibration-mode TEXT=online Choose 'online' or 'batch' --nr-active-frames INT=200 Maximum number of frames to be stored in the database. --keyframe-spacing INT=15 Number of frames that keyframes are apart in the backend optimizer. --min-keyframes-valid INT=3 Minimum number of frames a feature has to be tracked to be considered for optimization. ``` ## License This project was originally developed at the [TUM computer vision group](https://vision.in.tum.de) in 2017 by Paul Bergmann. It is currently maintained by Paul Bermann, [Rui Wang](https://vision.in.tum.de/members/wangr) and [Nikolaus Demmel](https://vision.in.tum.de/members/demmeln). See [AUTHROS.txt](AUTHORS.txt) for a list of contributors. This project is available under a BSD 3-Clause license. See [LICENSE.txt](LICENSE.txt) Among others, we make use of the following libraries: * OpenCV ([BSD](https://opencv.org/license.html)) * CLI11 ([BSD](https://github.com/CLIUtils/CLI11/blob/master/LICENSE)) ================================================ FILE: src/CLI11.hpp ================================================ #pragma once // CLI11: Version 1.5.3 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts // from: v1.5.3 // // From LICENSE: // // CLI11 1.5 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // 3. Neither the name of the copyright holder nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Standard combined includes: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Verbatim copy from CLI/Optional.hpp: #ifdef __has_include #if defined(CLI11_CPP17) && __has_include() && \ defined(__cpp_lib_optional) && !defined(CLI11_STD_OPTIONAL) #define CLI11_STD_OPTIONAL #endif #if defined(CLI11_CPP14) && __has_include() && \ !defined(CLI11_EXPERIMENTAL_OPTIONAL) #define CLI11_EXPERIMENTAL_OPTIONAL #endif #if __has_include() && !defined(CLI11_BOOST_OPTIONAL) #include #if BOOST_VERSION >= 105800 #define CLI11_BOOST_OPTIONAL #endif #endif #endif #ifdef CLI11_STD_OPTIONAL #include #endif #ifdef CLI11_EXPERIMENTAL_OPTIONAL #include #endif #ifdef CLI11_BOOST_OPTIONAL #include #endif // From CLI/Version.hpp: namespace CLI { // Note that all code in CLI11 must be in a namespace, even if it just a define. #define CLI11_VERSION_MAJOR 1 #define CLI11_VERSION_MINOR 5 #define CLI11_VERSION_PATCH 3 #define CLI11_VERSION "1.5.3" } // namespace CLI // From CLI/Macros.hpp: namespace CLI { // Note that all code in CLI11 must be in a namespace, even if it just a define. // The following version macro is very similar to the one in PyBind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) #if __cplusplus >= 201402L #define CLI11_CPP14 #if __cplusplus >= 201703L #define CLI11_CPP17 #if __cplusplus > 201703L #define CLI11_CPP20 #endif #endif #endif #elif defined(_MSC_VER) && __cplusplus == 199711L // MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) // Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer #if _MSVC_LANG >= 201402L #define CLI11_CPP14 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 #define CLI11_CPP17 #if __MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 #endif #endif #endif #endif #if defined(PYBIND11_CPP14) #define CLI11_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(_MSC_VER) #define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) #else #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif } // namespace CLI // From CLI/Optional.hpp: namespace CLI { #ifdef CLI11_STD_OPTIONAL template std::istream &operator>>(std::istream &in, std::optional &val) { T v; in >> v; val = v; return in; } #endif #ifdef CLI11_EXPERIMENTAL_OPTIONAL template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { T v; in >> v; val = v; return in; } #endif #ifdef CLI11_BOOST_OPTIONAL template std::istream &operator>>(std::istream &in, boost::optional &val) { T v; in >> v; val = v; return in; } #endif // Export the best optional to the CLI namespace #if defined(CLI11_STD_OPTIONAL) using std::optional; #elif defined(CLI11_EXPERIMENTAL_OPTIONAL) using std::experimental::optional; #elif defined(CLI11_BOOST_OPTIONAL) using boost::optional; #endif // This is true if any optional is found #if defined(CLI11_STD_OPTIONAL) || defined(CLI11_EXPERIMENTAL_OPTIONAL) || defined(CLI11_BOOST_OPTIONAL) #define CLI11_OPTIONAL #endif } // namespace CLI // From CLI/StringTools.hpp: namespace CLI { namespace detail { // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim inline std::vector split(const std::string &s, char delim) { std::vector elems; // Check to see if empty string, give consistent result if(s.empty()) elems.emplace_back(""); else { std::stringstream ss; ss.str(s); std::string item; while(std::getline(ss, item, delim)) { elems.push_back(item); } } return elems; } /// Simple function to join a string template std::string join(const T &v, std::string delim = ",") { std::ostringstream s; size_t start = 0; for(const auto &i : v) { if(start++ > 0) s << delim; s << i; } return s.str(); } /// Join a string in reverse order template std::string rjoin(const T &v, std::string delim = ",") { std::ostringstream s; for(size_t start = 0; start < v.size(); start++) { if(start > 0) s << delim; s << v[v.size() - start - 1]; } return s.str(); } // Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string /// Trim whitespace from left of string inline std::string <rim(std::string &str) { auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); str.erase(str.begin(), it); return str; } /// Trim anything from left of string inline std::string <rim(std::string &str, const std::string &filter) { auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); str.erase(str.begin(), it); return str; } /// Trim whitespace from right of string inline std::string &rtrim(std::string &str) { auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); str.erase(it.base(), str.end()); return str; } /// Trim anything from right of string inline std::string &rtrim(std::string &str, const std::string &filter) { auto it = std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); str.erase(it.base(), str.end()); return str; } /// Trim whitespace from string inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } /// Trim anything from string inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } /// Make a copy of the string and then trim it inline std::string trim_copy(const std::string &str) { std::string s = str; return trim(s); } /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) inline std::string trim_copy(const std::string &str, const std::string &filter) { std::string s = str; return trim(s, filter); } /// Print a two part "help" string inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; if(!description.empty()) { if(name.length() >= wid) out << std::endl << std::setw(static_cast(wid)) << ""; out << description; } out << std::endl; } /// Verify the first character of an option template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } /// Verify following characters of an option template bool valid_later_char(T c) { return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; } /// Verify an option name inline bool valid_name_string(const std::string &str) { if(str.empty() || !valid_first_char(str[0])) return false; for(auto c : str.substr(1)) if(!valid_later_char(c)) return false; return true; } /// Return a lower case version of a string inline std::string to_lower(std::string str) { std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { return std::tolower(x, std::locale()); }); return str; } /// Split a string '"one two" "three"' into 'one two', 'three' inline std::vector split_up(std::string str) { std::vector delims = {'\'', '\"'}; auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; trim(str); std::vector output; while(!str.empty()) { if(str[0] == '\'') { auto end = str.find('\'', 1); if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); str = str.substr(end + 1); } else { output.push_back(str.substr(1)); str = ""; } } else if(str[0] == '\"') { auto end = str.find('\"', 1); if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); str = str.substr(end + 1); } else { output.push_back(str.substr(1)); str = ""; } } else { auto it = std::find_if(std::begin(str), std::end(str), find_ws); if(it != std::end(str)) { std::string value = std::string(str.begin(), it); output.push_back(value); str = std::string(it, str.end()); } else { output.push_back(str); str = ""; } } trim(str); } return output; } /// Add a leader to the beginning of all new lines (nothing is added /// at the start of the first line). `"; "` would be for ini files /// /// Can't use Regex, or this would be a subs. inline std::string fix_newlines(std::string leader, std::string input) { std::string::size_type n = 0; while(n != std::string::npos && n < input.size()) { n = input.find('\n', n); if(n != std::string::npos) { input = input.substr(0, n + 1) + leader + input.substr(n + 1); n += leader.size(); } } return input; } } // namespace detail } // namespace CLI // From CLI/Error.hpp: namespace CLI { // Use one of these on all error classes #define CLI11_ERROR_DEF(parent, name) \ protected: \ name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ name(std::string name, std::string msg, ExitCodes exit_code) \ : parent(std::move(name), std::move(msg), exit_code) {} \ \ public: \ name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} // This is added after the one above if a class is used directly and builds its own message #define CLI11_ERROR_SIMPLE(name) \ explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} /// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, /// int values from e.get_error_code(). enum class ExitCodes { Success = 0, IncorrectConstruction = 100, BadNameString, OptionAlreadyAdded, FileError, ConversionError, ValidationError, RequiredError, RequiresError, ExcludesError, ExtrasError, INIError, InvalidError, HorribleError, OptionNotFound, ArgumentMismatch, BaseClass = 127 }; // Error definitions /// @defgroup error_group Errors /// @brief Errors thrown by CLI11 /// /// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. /// @{ /// All errors derive from this one class Error : public std::runtime_error { int exit_code; std::string name{"Error"}; public: int get_exit_code() const { return exit_code; } std::string get_name() const { return name; } Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} }; // Note: Using Error::Error constructors does not work on GCC 4.7 /// Construction errors (not in parsing) class ConstructionError : public Error { CLI11_ERROR_DEF(Error, ConstructionError) }; /// Thrown when an option is set to conflicting values (non-vector and multi args, for example) class IncorrectConstruction : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) CLI11_ERROR_SIMPLE(IncorrectConstruction) static IncorrectConstruction PositionalFlag(std::string name) { return IncorrectConstruction(name + ": Flags cannot be positional"); } static IncorrectConstruction Set0Opt(std::string name) { return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); } static IncorrectConstruction SetFlag(std::string name) { return IncorrectConstruction(name + ": Cannot set an expected number for flags"); } static IncorrectConstruction ChangeNotVector(std::string name) { return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); } static IncorrectConstruction AfterMultiOpt(std::string name) { return IncorrectConstruction( name + ": You can't change expected arguments after you've changed the multi option policy!"); } static IncorrectConstruction MissingOption(std::string name) { return IncorrectConstruction("Option " + name + " is not defined"); } static IncorrectConstruction MultiOptionPolicy(std::string name) { return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); } }; /// Thrown on construction of a bad name class BadNameString : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } static BadNameString DashesOnly(std::string name) { return BadNameString("Must have a name, not just dashes: " + name); } static BadNameString MultiPositionalNames(std::string name) { return BadNameString("Only one positional name allowed, remove: " + name); } }; /// Thrown when an option already exists class OptionAlreadyAdded : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) explicit OptionAlreadyAdded(std::string name) : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} static OptionAlreadyAdded Requires(std::string name, std::string other) { return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); } static OptionAlreadyAdded Excludes(std::string name, std::string other) { return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); } }; // Parsing errors /// Anything that can error in Parse class ParseError : public Error { CLI11_ERROR_DEF(Error, ParseError) }; // Not really "errors" /// This is a successful completion on parsing, supposed to exit class Success : public ParseError { CLI11_ERROR_DEF(ParseError, Success) Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} }; /// -h or --help on command line class CallForHelp : public ParseError { CLI11_ERROR_DEF(ParseError, CallForHelp) CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} }; /// Thrown when parsing an INI file and it is missing class FileError : public ParseError { CLI11_ERROR_DEF(ParseError, FileError) CLI11_ERROR_SIMPLE(FileError) static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } }; /// Thrown when conversion call back fails, such as when an int fails to coerce to a string class ConversionError : public ParseError { CLI11_ERROR_DEF(ParseError, ConversionError) CLI11_ERROR_SIMPLE(ConversionError) ConversionError(std::string member, std::string name) : ConversionError("The value " + member + " is not an allowed value for " + name) {} ConversionError(std::string name, std::vector results) : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} static ConversionError TooManyInputsFlag(std::string name) { return ConversionError(name + ": too many inputs for a flag"); } static ConversionError TrueFalse(std::string name) { return ConversionError(name + ": Should be true/false or a number"); } }; /// Thrown when validation of results fails class ValidationError : public ParseError { CLI11_ERROR_DEF(ParseError, ValidationError) CLI11_ERROR_SIMPLE(ValidationError) explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} }; /// Thrown when a required option is missing class RequiredError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} static RequiredError Subcommand(size_t min_subcom) { if(min_subcom == 1) return RequiredError("A subcommand"); else return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError); } }; /// Thrown when the wrong number of arguments has been received class ArgumentMismatch : public ParseError { CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) ArgumentMismatch(std::string name, int expected, size_t recieved) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + ", got " + std::to_string(recieved)) : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + ", got " + std::to_string(recieved)), ExitCodes::ArgumentMismatch) {} static ArgumentMismatch AtLeast(std::string name, int num) { return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); } }; /// Thrown when a requires option is missing class RequiresError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiresError) RequiresError(std::string curname, std::string subname) : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} }; /// Thrown when an excludes option is present class ExcludesError : public ParseError { CLI11_ERROR_DEF(ParseError, ExcludesError) ExcludesError(std::string curname, std::string subname) : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} }; /// Thrown when too many positionals or options are found class ExtrasError : public ParseError { CLI11_ERROR_DEF(ParseError, ExtrasError) explicit ExtrasError(std::vector args) : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} }; /// Thrown when extra values are found in an INI file class INIError : public ParseError { CLI11_ERROR_DEF(ParseError, INIError) CLI11_ERROR_SIMPLE(INIError) static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); } static INIError NotConfigurable(std::string item) { return INIError(item + ": This option is not allowed in a configuration file"); } }; /// Thrown when validation fails before parsing class InvalidError : public ParseError { CLI11_ERROR_DEF(ParseError, InvalidError) explicit InvalidError(std::string name) : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { } }; /// This is just a safety check to verify selection and parsing match - you should not ever see it /// Strings are directly added to this error, but again, it should never be seen. class HorribleError : public ParseError { CLI11_ERROR_DEF(ParseError, HorribleError) CLI11_ERROR_SIMPLE(HorribleError) }; // After parsing /// Thrown when counting a non-existent option class OptionNotFound : public Error { CLI11_ERROR_DEF(Error, OptionNotFound) explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} }; /// @} } // namespace CLI // From CLI/TypeTools.hpp: namespace CLI { // Type tools // We could check to see if C++14 is being used, but it does not hurt to redefine this // (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) // It is not in the std namespace anyway, so no harm done. template using enable_if_t = typename std::enable_if::type; template struct is_vector { static const bool value = false; }; template struct is_vector> { static bool const value = true; }; template struct is_bool { static const bool value = false; }; template <> struct is_bool { static bool const value = true; }; namespace detail { // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; // Type name print /// Was going to be based on /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template /// But this is cleaner and works better in this case template ::value && std::is_signed::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "INT"; } template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "UINT"; } template ::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "FLOAT"; } /// This one should not be used, since vector types print the internal type template ::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "VECTOR"; } template ::value && !std::is_integral::value && !is_vector::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; } // Lexical cast /// Signed integers / enums template ::value && std::is_signed::value), detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; long long output_ll = std::stoll(input, &n, 0); output = static_cast(output_ll); return n == input.size() && static_cast(output) == output_ll; } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// Unsigned integers template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { if(!input.empty() && input.front() == '-') return false; // std::stoull happily converts negative values to junk without any errors. try { size_t n = 0; unsigned long long output_ll = std::stoull(input, &n, 0); output = static_cast(output_ll); return n == input.size() && static_cast(output) == output_ll; } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// Floats template ::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; output = static_cast(std::stold(input, &n)); return n == input.size(); } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// String and similar template ::value && !std::is_integral::value && std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { output = input; return true; } /// Non-string parsable template ::value && !std::is_integral::value && !std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { std::istringstream is; is.str(input); is >> output; return !is.fail() && !is.rdbuf()->in_avail(); } } // namespace detail } // namespace CLI // From CLI/Split.hpp: namespace CLI { namespace detail { // Returns false if not a short option. Otherwise, sets opt name and rest and returns true inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { name = current.substr(1, 1); rest = current.substr(2); return true; } else return false; } // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { auto loc = current.find("="); if(loc != std::string::npos) { name = current.substr(2, loc - 2); value = current.substr(loc + 1); } else { name = current.substr(2); value = ""; } return true; } else return false; } // Splits a string into multiple long and short names inline std::vector split_names(std::string current) { std::vector output; size_t val; while((val = current.find(",")) != std::string::npos) { output.push_back(trim_copy(current.substr(0, val))); current = current.substr(val + 1); } output.push_back(trim_copy(current)); return output; } /// Get a vector of short names, one of long names, and a single name inline std::tuple, std::vector, std::string> get_names(const std::vector &input) { std::vector short_names; std::vector long_names; std::string pos_name; for(std::string name : input) { if(name.length() == 0) continue; else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { if(name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); else throw BadNameString::OneCharName(name); } else if(name.length() > 2 && name.substr(0, 2) == "--") { name = name.substr(2); if(valid_name_string(name)) long_names.push_back(name); else throw BadNameString::BadLongName(name); } else if(name == "-" || name == "--") { throw BadNameString::DashesOnly(name); } else { if(pos_name.length() > 0) throw BadNameString::MultiPositionalNames(name); pos_name = name; } } return std::tuple, std::vector, std::string>( short_names, long_names, pos_name); } } // namespace detail } // namespace CLI // From CLI/Ini.hpp: namespace CLI { namespace detail { inline std::string inijoin(std::vector args) { std::ostringstream s; size_t start = 0; for(const auto &arg : args) { if(start++ > 0) s << " "; auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); if(it == arg.end()) s << arg; else if(arg.find(R"(")") == std::string::npos) s << R"(")" << arg << R"(")"; else s << R"(')" << arg << R"(')"; } return s.str(); } struct ini_ret_t { /// This is the full name with dots std::string fullname; /// Listing of inputs std::vector inputs; /// Current parent level size_t level = 0; /// Return parent or empty string, based on level /// /// Level 0, a.b.c would return a /// Level 1, a.b.c could return b std::string parent() const { std::vector plist = detail::split(fullname, '.'); if(plist.size() > (level + 1)) return plist[level]; else return ""; } /// Return name std::string name() const { std::vector plist = detail::split(fullname, '.'); return plist.at(plist.size() - 1); } }; /// Internal parsing function inline std::vector parse_ini(std::istream &input) { std::string name, line; std::string section = "default"; std::vector output; while(getline(input, line)) { std::vector items; detail::trim(line); size_t len = line.length(); if(len > 1 && line[0] == '[' && line[len - 1] == ']') { section = line.substr(1, len - 2); } else if(len > 0 && line[0] != ';') { output.emplace_back(); ini_ret_t &out = output.back(); // Find = in string, split and recombine auto pos = line.find("="); if(pos != std::string::npos) { name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); items = detail::split_up(item); } else { name = detail::trim_copy(line); items = {"ON"}; } if(detail::to_lower(section) == "default") out.fullname = name; else out.fullname = section + "." + name; out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); } } return output; } /// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure inline std::vector parse_ini(const std::string &name) { std::ifstream input{name}; if(!input.good()) throw FileError::Missing(name); return parse_ini(input); } } // namespace detail } // namespace CLI // From CLI/Validators.hpp: namespace CLI { /// @defgroup validator_group Validators /// @brief Some validators that are provided /// /// These are simple `void(std::string&)` validators that are useful. They throw /// a ValidationError if they fail (or the normally expected error if the cast fails) /// @{ /// Check for an existing file inline std::string ExistingFile(const std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; bool is_dir = (buffer.st_mode & S_IFDIR) != 0; if(!exist) { return "File does not exist: " + filename; } else if(is_dir) { return "File is actually a directory: " + filename; } return std::string(); } /// Check for an existing directory inline std::string ExistingDirectory(const std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; bool is_dir = (buffer.st_mode & S_IFDIR) != 0; if(!exist) { return "Directory does not exist: " + filename; } else if(!is_dir) { return "Directory is actually a file: " + filename; } return std::string(); } /// Check for an existing path inline std::string ExistingPath(const std::string &filename) { struct stat buffer; bool const exist = stat(filename.c_str(), &buffer) == 0; if(!exist) { return "Path does not exist: " + filename; } return std::string(); } /// Check for a non-existing path inline std::string NonexistentPath(const std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; if(exist) { return "Path already exists: " + filename; } return std::string(); } /// Produce a range validator function template std::function Range(T min, T max) { return [min, max](std::string input) { T val; detail::lexical_cast(input, val); if(val < min || val > max) return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); return std::string(); }; } /// Range of one value is 0 to value template std::function Range(T max) { return Range(static_cast(0), max); } /// @} } // namespace CLI // From CLI/Option.hpp: namespace CLI { using results_t = std::vector; using callback_t = std::function; class Option; class App; using Option_p = std::unique_ptr