From b524ca0303ccaf452c3d59d66fa5cbb12975f4a8 Mon Sep 17 00:00:00 2001 From: Colm Donelan Date: Tue, 6 Oct 2020 15:15:33 +0100 Subject: IVGCVSW-5213 Switch armnnConverter over to cxxopts * Swap out boost::program_options with cxxopts. * Remove const from argv declaration to conform to cxxopts. * Clean up CLI handling around multiple inputs and shapes. Each input must be specified with its own -i param. Each corresponding -s parameter must be specified too. Signed-off-by: Colm Donelan Change-Id: I01c2683dbca51b2322c98913731ac82a59e400a5 --- src/armnnConverter/ArmnnConverter.cpp | 175 ++++++++++++++++------------------ 1 file changed, 81 insertions(+), 94 deletions(-) diff --git a/src/armnnConverter/ArmnnConverter.cpp b/src/armnnConverter/ArmnnConverter.cpp index c8b9ba9f28..b5d3256c3c 100644 --- a/src/armnnConverter/ArmnnConverter.cpp +++ b/src/armnnConverter/ArmnnConverter.cpp @@ -22,10 +22,18 @@ #include #include -#include "armnn/utility/StringUtils.hpp" +#include + +/* + * Historically we use the ',' character to separate dimensions in a tensor shape. However, cxxopts will read this + * an an array of values which is fine until we have multiple tensors specified. This lumps the values of all shapes + * together in a single array and we cannot break it up again. We'll change the vector delimiter to a '.'. We do this + * as close as possible to the usage of cxxopts to avoid polluting other possible uses. + */ +#define CXXOPTS_VECTOR_DELIMITER '.' +#include #include -#include #include #include @@ -34,8 +42,6 @@ namespace { -namespace po = boost::program_options; - armnn::TensorShape ParseTensorShape(std::istream& stream) { std::vector result; @@ -63,46 +69,7 @@ armnn::TensorShape ParseTensorShape(std::istream& stream) return armnn::TensorShape(armnn::numeric_cast(result.size()), result.data()); } -bool CheckOption(const po::variables_map& vm, - const char* option) -{ - if (option == nullptr) - { - return false; - } - - // Check whether 'option' is provided. - return vm.find(option) != vm.end(); -} - -void CheckOptionDependency(const po::variables_map& vm, - const char* option, - const char* required) -{ - if (option == nullptr || required == nullptr) - { - throw po::error("Invalid option to check dependency for"); - } - - // Check that if 'option' is provided, 'required' is also provided. - if (CheckOption(vm, option) && !vm[option].defaulted()) - { - if (CheckOption(vm, required) == 0 || vm[required].defaulted()) - { - throw po::error(std::string("Option '") + option + "' requires option '" + required + "'."); - } - } -} - -void CheckOptionDependencies(const po::variables_map& vm) -{ - CheckOptionDependency(vm, "model-path", "model-format"); - CheckOptionDependency(vm, "model-path", "input-name"); - CheckOptionDependency(vm, "model-path", "output-name"); - CheckOptionDependency(vm, "input-tensor-shape", "model-path"); -} - -int ParseCommandLineArgs(int argc, const char* argv[], +int ParseCommandLineArgs(int argc, char* argv[], std::string& modelFormat, std::string& modelPath, std::vector& inputNames, @@ -110,64 +77,92 @@ int ParseCommandLineArgs(int argc, const char* argv[], std::vector& outputNames, std::string& outputPath, bool& isModelBinary) { - po::options_description desc("Options"); - - desc.add_options() - ("help", "Display usage information") - ("model-format,f", po::value(&modelFormat)->required(),"Format of the model file" + cxxopts::Options options("ArmNNConverter", "Convert a neural network model from provided file to ArmNN format."); + try + { + std::string modelFormatDescription("Format of the model file"); #if defined(ARMNN_CAFFE_PARSER) - ", caffe-binary, caffe-text" + modelFormatDescription += ", caffe-binary, caffe-text"; #endif #if defined(ARMNN_ONNX_PARSER) - ", onnx-binary, onnx-text" + modelFormatDescription += ", onnx-binary, onnx-text"; #endif #if defined(ARMNN_TF_PARSER) - ", tensorflow-binary, tensorflow-text" + modelFormatDescription += ", tensorflow-binary, tensorflow-text"; #endif #if defined(ARMNN_TF_LITE_PARSER) - ", tflite-binary" + modelFormatDescription += ", tflite-binary"; #endif - ".") - ("model-path,m", po::value(&modelPath)->required(), "Path to model file.") - ("input-name,i", po::value>()->multitoken(), - "Identifier of the input tensors in the network, separated by whitespace.") - ("input-tensor-shape,s", po::value>()->multitoken(), - "The shape of the input tensor in the network as a flat array of integers, separated by comma." - " Multiple shapes are separated by whitespace." - " This parameter is optional, depending on the network.") - ("output-name,o", po::value>()->multitoken(), - "Identifier of the output tensor in the network.") - ("output-path,p", po::value(&outputPath)->required(), "Path to serialize the network to."); - - po::variables_map vm; - try - { - po::store(po::parse_command_line(argc, argv, desc), vm); - - if (CheckOption(vm, "help") || argc <= 1) - { - std::cout << "Convert a neural network model from provided file to ArmNN format." << std::endl; - std::cout << std::endl; - std::cout << desc << std::endl; - exit(EXIT_SUCCESS); - } - po::notify(vm); + modelFormatDescription += "."; + options.add_options() + ("help", "Display usage information") + ("f,model-format", modelFormatDescription, cxxopts::value(modelFormat)) + ("m,model-path", "Path to model file.", cxxopts::value(modelPath)) + + ("i,input-name", "Identifier of the input tensors in the network. " + "Each input must be specified separately.", + cxxopts::value>(inputNames)) + ("s,input-tensor-shape", + "The shape of the input tensor in the network as a flat array of integers, " + "separated by comma. Each input shape must be specified separately after the input name. " + "This parameter is optional, depending on the network.", + cxxopts::value>(inputTensorShapeStrs)) + + ("o,output-name", "Identifier of the output tensor in the network.", + cxxopts::value>(outputNames)) + ("p,output-path", + "Path to serialize the network to.", cxxopts::value(outputPath)); } - catch (const po::error& e) + catch (const std::exception& e) { - std::cerr << e.what() << std::endl << std::endl; - std::cerr << desc << std::endl; + std::cerr << e.what() << std::endl << options.help() << std::endl; return EXIT_FAILURE; } - try { - CheckOptionDependencies(vm); + cxxopts::ParseResult result = options.parse(argc, argv); + if (result.count("help")) + { + std::cerr << options.help() << std::endl; + return EXIT_SUCCESS; + } + // Check for mandatory single options. + std::string mandatorySingleParameters[] = { "model-format", "model-path", "output-name", "output-path" }; + bool somethingsMissing = false; + for (auto param : mandatorySingleParameters) + { + if (result.count(param) != 1) + { + std::cerr << "Parameter \'--" << param << "\' is required but missing." << std::endl; + somethingsMissing = true; + } + } + // Check at least one "input-name" option. + if (result.count("input-name") == 0) + { + std::cerr << "Parameter \'--" << "input-name" << "\' must be specified at least once." << std::endl; + somethingsMissing = true; + } + // If input-tensor-shape is specified then there must be a 1:1 match with input-name. + if (result.count("input-tensor-shape") > 0) + { + if (result.count("input-tensor-shape") != result.count("input-name")) + { + std::cerr << "When specifying \'input-tensor-shape\' a matching number of \'input-name\' parameters " + "must be specified." << std::endl; + somethingsMissing = true; + } + } + + if (somethingsMissing) + { + std::cerr << options.help() << std::endl; + return EXIT_FAILURE; + } } - catch (const po::error& e) + catch (const cxxopts::OptionException& e) { std::cerr << e.what() << std::endl << std::endl; - std::cerr << desc << std::endl; return EXIT_FAILURE; } @@ -185,14 +180,6 @@ int ParseCommandLineArgs(int argc, const char* argv[], return EXIT_FAILURE; } - if (!vm["input-tensor-shape"].empty()) - { - inputTensorShapeStrs = vm["input-tensor-shape"].as>(); - } - - inputNames = vm["input-name"].as>(); - outputNames = vm["output-name"].as>(); - return EXIT_SUCCESS; } @@ -346,7 +333,7 @@ private: } // anonymous namespace -int main(int argc, const char* argv[]) +int main(int argc, char* argv[]) { #if (!defined(ARMNN_CAFFE_PARSER) \ -- cgit v1.2.1