From c508cf7503fd97e616c12e54f5975a7238ab5987 Mon Sep 17 00:00:00 2001 From: Tristan Laurent <tristan.laurent@cs-soprasteria.com> Date: Fri, 14 Mar 2025 19:31:11 +0100 Subject: [PATCH 1/2] COMP: QGIS descriptor generator now takes a list of application --- Modules/Core/Convolution/CMakeLists.txt | 2 +- Modules/Core/Wrappers/QGIS/src/CMakeLists.txt | 33 +- .../Wrappers/QGIS/src/otbQgisDescriptor.cxx | 606 +++++++++--------- 3 files changed, 340 insertions(+), 301 deletions(-) diff --git a/Modules/Core/Convolution/CMakeLists.txt b/Modules/Core/Convolution/CMakeLists.txt index 262a67e778..23143bcc31 100644 --- a/Modules/Core/Convolution/CMakeLists.txt +++ b/Modules/Core/Convolution/CMakeLists.txt @@ -22,7 +22,7 @@ project(OTBConvolution) option(OTB_USE_FFTW "Download and compile FFTW third party (license change to GPLv2)" OFF) if (OTB_USE_FFTW) - message(NOTICE "/!\\ /!\\ /!\\ You are actually compiling OTB with FFTW. As FFTW is distributed under GNU GPLv2, OTB is now provided with GNU GPLv2 license /!\\ /!\\ /!\\ ") + message(AUTHOR_WARNING "/!\\ /!\\ /!\\ You are actually compiling OTB with FFTW. As FFTW is distributed under GNU GPLv2, OTB is now provided with GNU GPLv2 license /!\\ /!\\ /!\\ ") else() message(STATUS "Compiling with FFTW disabled, otbOverlapSaveConvolutionImageFilter will not be available") endif() diff --git a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt index 5dcf0ceffd..d7f7bc375b 100644 --- a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt +++ b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt @@ -42,17 +42,32 @@ set(generate_descriptor_cmd "$<TARGET_FILE:otbQgisDescriptor>") if(CMAKE_SKIP_RPATH AND "${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(generate_descriptor_cmd "env;LD_LIBRARY_PATH=${OTB_BINARY_DIR}/lib;$<TARGET_FILE:otbQgisDescriptor>") endif() +# generate dependency list +# put all app into a file +# same for dfiles +set(QGIS_APP_LIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps_list.txt") +file(TOUCH "${QGIS_APP_LIST_FILE}") +set(QGIS_APP_DESCRIPTOR_PATH "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description") foreach(app_name ${app_names}) add_dependencies(otbQgisDescriptor otbapp_${app_name}) + file(APPEND "${QGIS_APP_LIST_FILE}" "${app_name}\n") set(dfile "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description/${app_name}.txt") - add_custom_command(OUTPUT "${dfile}" - COMMAND ${generate_descriptor_cmd} - "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" - WORKING_DIRECTORY ${OTB_BINARY_DIR} - COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/" - VERBATIM) - list(APPEND dfiles "${dfile}") + # add_custom_command(OUTPUT "${dfile}" + # COMMAND ${generate_descriptor_cmd} + # "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" + # WORKING_DIRECTORY ${OTB_BINARY_DIR} + # COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/" + # VERBATIM) endforeach() -add_custom_target(generate_descriptors DEPENDS ${dfiles} ) -add_dependencies(${otb-module}-all generate_descriptors) +# call otbQgisDescriptor with the app list, the location to create the files +# add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt" +# COMMAND ${generate_descriptor_cmd} +# "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps.txt" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" +# WORKING_DIRECTORY ${OTB_BINARY_DIR} +# COMMENT "Generate gqis descriptor files" +# VERBATIM) + + +# add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt") +# add_dependencies(${otb-module}-all generate_descriptors) diff --git a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx index 83fa26d184..1579bd317e 100644 --- a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx +++ b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx @@ -28,339 +28,363 @@ #include <iostream> #include <cassert> #include <fstream> - -int main(int argc, char* argv[]) +#include <regex> + +#define QGIS_DEBUG 0 + +using namespace otb::Wrapper; +static const std::unordered_map<ParameterType, std::string> parameterTypeToString { + {ParameterType_Bool, "QgsProcessingParameterBoolean"}, + {ParameterType_Int, "QgsProcessingParameterNumber"}, + {ParameterType_Float, "QgsProcessingParameterNumber"}, + {ParameterType_Double, "QgsProcessingParameterNumber"}, + {ParameterType_RAM, "QgsProcessingParameterNumber"}, + {ParameterType_Radius, "QgsProcessingParameterNumber"}, + {ParameterType_Choice, "OTBParameterChoice"}, + {ParameterType_String, "QgsProcessingParameterString"}, + {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"}, + {ParameterType_InputFilename, "QgsProcessingParameterFile"}, + {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"}, + {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"}, + {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"}, + {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"}, + {ParameterType_Directory, "QgsProcessingParameterFile"}, + {ParameterType_Field, "QgsProcessingParameterField"}, + {ParameterType_Band, "QgsProcessingParameterBand"}, + {ParameterType_StringList, "QgsProcessingParameterString"}, + {ParameterType_ListView, "QgsProcessingParameterEnum"} +}; + +bool addModuleToAlgorithmList(std::ofstream& algs_list_file, + const std::string& module, + const std::string& group) { - if (argc < 3) - { - std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl; - return EXIT_FAILURE; + if (algs_list_file) { + algs_list_file << group << "|" << module << std::endl; + return algs_list_file.good(); + } + else { + return false; } +} - using namespace otb::Wrapper; +std::string getMandatoryOutputImageParam(const Application::Pointer app, + const std::vector<std::string>& app_keys) { + for (const std::string& app_key: app_keys) + { + Parameter::Pointer param = app->GetParameterByKey(app_key); + if (param->GetMandatory() && param->GetType() == ParameterType_OutputImage) + { + return app_key; + } + } + return ""; // if nothing found +} - const std::string module(argv[1]); +std::string QGISLineFactory(const Application::Pointer app, + const std::string& paramKey) +{ + std::ostringstream qgis_line; + const Parameter::Pointer param (app->GetParameterByKey(paramKey)); + ParameterType type (param->GetType()); + const std::string description (app->GetParameterName(paramKey)); + std::string optional ("True"); + std::string default_value ("None"); -/* TestApplication is removed in CMakeLists.txt */ -#if 0 - if (module == "TestApplication") - return EXIT_SUCCESS; -#endif - ApplicationRegistry::AddApplicationPath(argv[2]); - Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module); - - assert(!appli.IsNull()); + if (type == ParameterType_Group || type == ParameterType_RAM || param->GetRole() == Role_Output) + { + // group parameter cannot have any value. + // ram is added by qgis-otb processing provider plugin as an advanced parameter for all apps + // parameter role cannot be of type Role_Output + return ""; + } - const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : ""; - assert(!group.empty()); + // if there is no choice value for a list view parameter + // we switch to parameter stringlist to avoid aving empty combobox in QGIS + if (type == ParameterType_ListView && app->GetChoiceNames(paramKey).empty()) { + type = ParameterType_StringList; + } - std::map<ParameterType, std::string> parameterTypeToString; - parameterTypeToString[ParameterType_Bool] = "QgsProcessingParameterBoolean"; - parameterTypeToString[ParameterType_Int] = "QgsProcessingParameterNumber"; - parameterTypeToString[ParameterType_Float] = "QgsProcessingParameterNumber"; - parameterTypeToString[ParameterType_Double] = "QgsProcessingParameterNumber"; - parameterTypeToString[ParameterType_RAM] = "QgsProcessingParameterNumber"; - parameterTypeToString[ParameterType_Radius] = "QgsProcessingParameterNumber"; - parameterTypeToString[ParameterType_Choice] = "OTBParameterChoice"; - parameterTypeToString[ParameterType_String] = "QgsProcessingParameterString"; - parameterTypeToString[ParameterType_InputImage] = "QgsProcessingParameterRasterLayer"; - parameterTypeToString[ParameterType_InputFilename] = "QgsProcessingParameterFile"; - parameterTypeToString[ParameterType_InputImageList] = "QgsProcessingParameterMultipleLayers"; - parameterTypeToString[ParameterType_InputVectorData] = "QgsProcessingParameterVectorLayer"; - parameterTypeToString[ParameterType_InputFilenameList] = "QgsProcessingParameterMultipleLayers"; - parameterTypeToString[ParameterType_InputVectorDataList] = "QgsProcessingParameterMultipleLayers"; - parameterTypeToString[ParameterType_OutputImage] = "QgsProcessingParameterRasterDestination"; - parameterTypeToString[ParameterType_OutputVectorData] = "QgsProcessingParameterVectorDestination"; - parameterTypeToString[ParameterType_OutputFilename] = "QgsProcessingParameterFileDestination"; - parameterTypeToString[ParameterType_Directory] = "QgsProcessingParameterFile"; - parameterTypeToString[ParameterType_Field] = "QgsProcessingParameterField"; - parameterTypeToString[ParameterType_Band] = "QgsProcessingParameterBand"; - // TODO - parameterTypeToString[ParameterType_StringList] = "QgsProcessingParameterString"; - - parameterTypeToString[ParameterType_ListView] = "QgsProcessingParameterEnum"; - - const std::vector<std::string> appKeyList = appli->GetParametersKeys(true); - const unsigned int nbOfParam = appKeyList.size(); - std::string output_file = module + ".txt"; - std::string algs_txt = "algs.txt"; - if (argc > 3) + auto it = parameterTypeToString.find(type); + if (it == parameterTypeToString.end()) { - output_file = std::string(argv[3]) + module + ".txt"; - algs_txt = std::string(argv[3]) + "algs.txt"; + // NOTE get also module name + throw std::runtime_error("No mapping found for parameter '" + paramKey + + "' type=" + parameterTypesStrings[type]); } - std::ofstream dFile; - dFile.open(output_file, std::ios::out); + + std::string qgis_type = it->second; - std::string output_parameter_name; - bool hasRasterOutput = false; + if (param->GetMandatory()) { - for (unsigned int i = 0; i < nbOfParam; i++) - { - Parameter::Pointer param = appli->GetParameterByKey(appKeyList[i]); - if (param->GetMandatory()) - { - if (appli->GetParameterType(appKeyList[i]) == ParameterType_OutputImage) - { - output_parameter_name = appKeyList[i]; - hasRasterOutput = true; - } - } - } + // TODO: avoid workaround for stringlist types (fix appengine) + // type == ParameterType_StringList check is needed because: + // If parameter is mandatory it can have no value + // It is accepted in OTB that, string list could be generated dynamically + // qgis has no such option to handle dynamic values yet.. + // So mandatory parameters whose type is StringList is considered optional + optional = param->HasValue() || type == ParameterType_StringList ? "True" : "False"; } - if (output_parameter_name.empty()) - dFile << module << std::endl; - else - dFile << module << "|" << output_parameter_name << std::endl; - - dFile << appli->GetDescription() << std::endl; - dFile << group << std::endl; - /* - From here onwards each line appended to dFile is passed to QgsProcessingParameter* class constructor. - dFile is nothing but a csv (with a .txt) with "|" as delimiter character. - First field is the name of Qgis parameter class and rest of it are extracted as list and passed to class constructor. - Parsing and calling of parameter classes are done by python. - source available : qgis/python/plugins/processing/core/parameters.py - source code of qgis parameter is available at: qgis/src/core/processing/qgsprocessingparameters.cpp - */ + bool isEpsgCode = false; - for (unsigned int i = 0; i < nbOfParam; i++) + // use QgsProcessingParameterCrs if required. + // CHeck if paramKey ==epsg only or epsg is one onf the extension + const std::regex epsg_regex("*\.epsg\.*", std::regex_constants::grep); + std::cmatch tmp_match; // useless param just for function + if (paramKey == "epsg" || std::regex_match(paramKey, tmp_match, epsg_regex)) { - const std::string name = appKeyList[i]; - const Parameter::Pointer param = appli->GetParameterByKey(name); - ParameterType type = appli->GetParameterType(name); - const std::string description = appli->GetParameterName(name); - - // if there is no choice value for a list view parameter - // we switch to parameter stringlist to avoid aving empty combobox in QGIS - if (type == ParameterType_ListView) - { - if ( appli->GetChoiceNames(name).empty() ) - { - type = ParameterType_StringList; - } - } + qgis_type = "QgsProcessingParameterCrs"; + isEpsgCode = true; + } + + qgis_line << qgis_type << "|" << paramKey << "|" << description; - std::string optional; - if (param->GetMandatory()) + std::vector<std::string> name_list; + std::vector<std::string> key_list; + std::string values; + switch (type) + { + case ParameterType_Int: + if (isEpsgCode) { - // TODO: avoid workaround for stringlist types (fix appengine) - // type == ParameterType_StringList check is needed because: - // If parameter is mandatory it can have no value - // It is accepted in OTB that, string list could be generated dynamically - // qgis has no such option to handle dynamic values yet.. - // So mandatory parameters whose type is StringList is considered optional - optional = param->HasValue() || type == ParameterType_StringList ? "True" : "False"; + if (param->HasValue() && app->GetParameterInt(paramKey) < 1) + default_value = "EPSG: " + app->GetParameterAsString(paramKey); + else + default_value = "ProjectCrs"; } else { - optional = "True"; + qgis_line << "|QgsProcessingParameterNumber.Integer"; + default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0"; } - - if (type == ParameterType_Group || type == ParameterType_RAM || param->GetRole() == Role_Output) + break; + case ParameterType_Float: + case ParameterType_Double: + qgis_line << "|QgsProcessingParameterNumber.Double"; + default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0"; + break; + case ParameterType_Radius: + qgis_line << "|QgsProcessingParameterNumber.Integer"; + default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0"; + break; + case ParameterType_InputFilename: + // TODO: if parameter InputFilename can give supported extensions + // we can use it gitlab #1559 + qgis_line << "|QgsProcessingParameterFile.File|None"; + break; + case ParameterType_Directory: + qgis_line << "|QgsProcessingParameterFile.Folder|False"; + break; + case ParameterType_InputImageList: + qgis_line << "|3"; // QgsProcessing.TypeRaster + break; + case ParameterType_InputVectorDataList: + case ParameterType_InputVectorData: + case ParameterType_OutputVectorData: + qgis_line << "|-1"; // QgsProcessing.TypeVectorAnyGeometry + break; + case ParameterType_InputFilenameList: + qgis_line << "|4"; // QgsProcessing.TypeFile" + break; + case ParameterType_String: + // Below line is interpreted in qgis processing as + // 1. default_value = None + // 2. multiLine = False + // For more details, + // please refer to documentation of QgsProcessingParameterString. + default_value = "None|False"; + break; + case ParameterType_StringList: + // Below line is interpreted in qgis processing as + // 1. default_value = None + // 2. multiLine = True + // For more details, + // please refer to documentation of QgsProcessingParameterString. + // setting default_value this way is an exception for ParameterType_StringList and ParameterType_String + default_value = "None|True"; + break; + case ParameterType_InputImage: + case ParameterType_OutputImage: + // default is None and nothing to add to qgis_line + break; + case ParameterType_OutputFilename: + // fileFilter and defaultValue is None + // OTB does not have any implementation for file extension. + default_value = "None|None"; + break; + case ParameterType_ListView: + ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer()); + name_list = app->GetChoiceNames(paramKey); + for( auto k : name_list) + values += k + ";"; + values.pop_back(); + qgis_line << "|" << values ; + qgis_line << "|" << (lv_param->GetSingleSelection() ? "False" : "True"); + break; + case ParameterType_Bool: + default_value = app->GetParameterAsString(paramKey); + break; + case ParameterType_Choice: + key_list = app->GetChoiceKeys(paramKey); + for (auto k : key_list) + values += k + ";"; + + if (!values.empty()) + values.pop_back(); + + qgis_line << "|" << values; + ChoiceParameter* choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer()); + default_value = std::to_string(choiceParam->GetValue()); + break; + case ParameterType_Field: + FieldParameter* fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer()); + + enum { + STRING = 0b00000001, + NUMERIC = 0b00000010 + }; + + int filterType = 0b00000000; + for (auto type : fieldParam->GetTypeFilter()) { - // group parameter cannot have any value. - // ram is added by qgis-otb processing provider plugin as an advanced parameter for all apps - // parameter role cannot be of type Role_Output - continue; + if (type == OFTString) + filterType |= STRING; + else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal) + filterType |= NUMERIC; } - auto it = parameterTypeToString.find(type); - assert(it != parameterTypeToString.end()); - if (it == parameterTypeToString.end()) - { - std::cerr << "No mapping found for parameter '" << name << "' type=" << type << std::endl; - return EXIT_FAILURE; - } - std::string qgis_type = it->second; + qgis_line << "|None|" + << fieldParam->GetVectorData() + << "|" << (filterType == STRING ? "QgsProcessingParameterField.String" : + filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" : + "QgsProcessingParameterField.Any") + << "|" << (fieldParam->GetSingleSelection() ? "False" : "True") + << "|" << optional; + break; + case ParameterType_Band: + BandParameter* bandParam = dynamic_cast<BandParameter*>(param.GetPointer()); + + qgis_line << "|None|" + << bandParam->GetRasterData() << "|" + << optional + << "|" << (bandParam->GetSingleSelection() ? "False" : "True"); + break; + default: + throw std::runtime_error("ERROR: default_value is empty for '" + paramKey + + "' type='" + qgis_type + "'"); + break; + } - bool isEpsgCode = false; +#if QGIS_DEBUG + std::cerr << paramKey; + std::cerr << " mandatory=" << param->GetMandatory(); + std::cerr << " HasValue=" << param->HasValue(); + std::cerr << " qgis_type=" << qgis_type; + std::cerr << " optional=" << optional << std::endl; +#endif - // use QgsProcessingParameterCrs if required. - // TODO: do a regex on name to match ==epsg || *\.epsg.\* - if (name == "epsg" || name == "map.epsg.code" || name == "mapproj.epsg.code" || name == "mode.epsg.code") - { - qgis_type = "QgsProcessingParameterCrs"; - isEpsgCode = true; - } + // optional and default value are not in the same order than + // other QGis processing parameters for field and band + if (type != ParameterType_Field && type != ParameterType_Band) + { + qgis_line << "|" << default_value << "|" << optional; + } - dFile << qgis_type << "|" << name << "|" << description; + return qgis_line.str(); +} - std::string default_value = "None"; - if (type == ParameterType_Int) - { - if (isEpsgCode) - { - if (param->HasValue() && appli->GetParameterInt(name) < 1) - default_value = "EPSG: " + appli->GetParameterAsString(name); - else - default_value = "ProjectCrs"; - } - else - { - dFile << "|QgsProcessingParameterNumber.Integer"; - default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0"; - } - } - else if (type == ParameterType_Float || type == ParameterType_Double) - { - dFile << "|QgsProcessingParameterNumber.Double"; - default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0"; - } - else if (type == ParameterType_Radius) - { - dFile << "|QgsProcessingParameterNumber.Integer"; - default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0"; - } - else if (type == ParameterType_InputFilename) - { - // TODO: if parameter InputFilename can give supported extensions - // we can use it gitlab #1559 - dFile << "|QgsProcessingParameterFile.File|None"; - } - else if (type == ParameterType_Directory) - { - dFile << "|QgsProcessingParameterFile.Folder|False"; - } - else if (type == ParameterType_InputImageList) - { - dFile << "|3"; // QgsProcessing.TypeRaster - } - else if (type == ParameterType_InputVectorDataList || type == ParameterType_InputVectorData || type == ParameterType_OutputVectorData) - { - dFile << "|-1"; // QgsProcessing.TypeVectorAnyGeometry - } - else if (type == ParameterType_InputFilenameList) - { - dFile << "|4"; // QgsProcessing.TypeFile" - } - else if (type == ParameterType_String) - { - // Below line is interpreted in qgis processing as - // 1. default_value = None - // 2. multiLine = False - // For more details, - // please refer to documentation of QgsProcessingParameterString. - default_value = "None|False"; - } - else if (type == ParameterType_StringList) - { - // Below line is interpreted in qgis processing as - // 1. default_value = None - // 2. multiLine = True - // For more details, - // please refer to documentation of QgsProcessingParameterString. - // setting default_value this way is an exception for ParameterType_StringList and ParameterType_String - default_value = "None|True"; - } - else if (type == ParameterType_InputImage || type == ParameterType_OutputImage) - { - // default is None and nothing to add to dFile - } - else if (type == ParameterType_OutputFilename) - { - // fileFilter and defaultValue is None - // OTB does not have any implementation for file extension. - default_value = "None|None"; - } - else if (type == ParameterType_ListView) - { - ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer()); - std::vector<std::string> name_list = appli->GetChoiceNames(name); - std::string values = ""; - for( auto k : name_list) - values += k + ";"; - values.pop_back(); - dFile << "|" << values ; - dFile << "|" << (lv_param->GetSingleSelection() ? "False" : "True"); - } - else if (type == ParameterType_Bool) - { - default_value = appli->GetParameterAsString(name); - } - else if (type == ParameterType_Choice) - { - std::vector<std::string> key_list = appli->GetChoiceKeys(name); - std::string values = ""; - for (auto k : key_list) - values += k + ";"; +bool writeModuleQGISDescriptor(const Application::Pointer app, + const std::string& module_name, + const std::string& group, + const std::string& descriptor_file_path) { + // RAII capsule to automatically close file when we are out of scope + auto module_descriptor = std::unique_ptr<std::ofstream, std::function<void(std::ofstream*)>>( + new std::ofstream(descriptor_file_path, std::ios::out), + [](std::ofstream* o) { o->close(); } + ); + + const std::vector<std::string> appKeyList = app->GetParametersKeys(true); + + // --------------QGIS Module file header----------------- + // composed like following (line break included): + // <module name>|<mandatory output param name (if exists)> + // <description> + // <group> + const std::string mandatory_out_param_name = getMandatoryOutputImageParam(app, appKeyList); + + // Add app name and mandatory output if it exists + if (mandatory_out_param_name.empty()) + *module_descriptor << module_name << std::endl; + else + *module_descriptor << module_name << "|" << mandatory_out_param_name << std::endl; - if (!values.empty()) - values.pop_back(); + // add description and group + *module_descriptor << app->GetDescription() << std::endl; + *module_descriptor << group << std::endl; - dFile << "|" << values; - ChoiceParameter* cparam = dynamic_cast<ChoiceParameter*>(param.GetPointer()); - default_value = std::to_string(cparam->GetValue()); - } - else if (type == ParameterType_Field) - { - FieldParameter *f_param = dynamic_cast<FieldParameter*>(param.GetPointer()); - - enum { - STRING = 1 << 0, - NUMERIC = 1 << 1 - }; - - int filterType = 0; - for (auto type : f_param->GetTypeFilter()) - { - if (type == OFTString) - filterType |= STRING; - else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal) - filterType |= NUMERIC; - } - - dFile << "|None|" - << f_param->GetVectorData() - << "|" << (filterType == STRING ? "QgsProcessingParameterField.String" : - filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" : - "QgsProcessingParameterField.Any") - << "|" << (f_param->GetSingleSelection() ? "False" : "True") - << "|" << optional; - } - else if (type == ParameterType_Band) - { - BandParameter *f_param = dynamic_cast<BandParameter*>(param.GetPointer()); + //-------------QGIS Module parameters part---------------- - dFile << "|None|" - << f_param->GetRasterData() << "|" - << optional - << "|" << (f_param->GetSingleSelection() ? "False" : "True"); - } - else - { - std::cout << "ERROR: default_value is empty for '" << name << "' type='" << qgis_type << "'" << std::endl; - return EXIT_FAILURE; - } + /* + From here onwards each line appended to module_descriptor is passed to QgsProcessingParameter* class constructor. + module_descriptor is nothing but a csv (with a .txt) with "|" as delimiter character. + First field is the name of Qgis parameter class and rest of it are extracted as list and passed to class constructor. + Parsing and calling of parameter classes are done by python. + source available : qgis/python/plugins/processing/core/parameters.py + source code of qgis parameter is available at: qgis/src/core/processing/qgsprocessingparameters.cpp + */ + std::string param_line; + for (const std::string& paramKey: appKeyList) { + // the following function can throw, but let is thow upper + param_line = QGISLineFactory(app, paramKey); + if (!param_line.empty()) + *module_descriptor << param_line << std::endl; + } -#if 0 - std::cerr << name; - std::cerr << " mandatory=" << param->GetMandatory(); - std::cerr << " HasValue=" << param->HasValue(); - std::cerr << " qgis_type=" << qgis_type; - std::cerr << " optional=" << optional << std::endl; -#endif + if (!mandatory_out_param_name.empty()) + { + *module_descriptor << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl; + } +} - // optional and default value are not in the same order than - // other QGis processing parameters for field and band - if (type != ParameterType_Field && type != ParameterType_Band) - { - dFile << "|" << default_value << "|" << optional; - } - dFile << std::endl; + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl; + return EXIT_FAILURE; } - if (hasRasterOutput) + const std::string module(argv[1]); + + ApplicationRegistry::AddApplicationPath(argv[2]); + Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module); + + assert(!appli.IsNull()); + + std::string output_file = module + ".txt"; + std::string algs_txt = "algs.txt"; + + if (argc > 3) { - dFile << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl; + output_file = std::string(argv[3]) + module + ".txt"; + algs_txt = std::string(argv[3]) + "algs.txt"; } + const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : ""; + assert(!group.empty()); + + // write module + writeModuleQGISDescriptor(appli, module, group, output_file); + std::cerr << "Writing " << output_file << std::endl; - dFile.close(); std::ofstream indexFile; indexFile.open(algs_txt, std::ios::out | std::ios::app); -- GitLab From 7eeded5110f0bf2273bfcd29e84e60c698dee80a Mon Sep 17 00:00:00 2001 From: Tristan Laurent <tristan.laurent@cs-soprasteria.com> Date: Mon, 17 Mar 2025 16:17:05 +0100 Subject: [PATCH 2/2] COMP: changes on otbQgisDescriptor avoiding concurrent access on algs.txt --- Modules/Core/Wrappers/QGIS/src/CMakeLists.txt | 27 +-- .../Wrappers/QGIS/src/otbQgisDescriptor.cxx | 198 ++++++++++-------- 2 files changed, 125 insertions(+), 100 deletions(-) diff --git a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt index d7f7bc375b..db199d5964 100644 --- a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt +++ b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt @@ -46,28 +46,23 @@ endif() # put all app into a file # same for dfiles set(QGIS_APP_LIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps_list.txt") +# if file does not exists it does not emit an error +file(REMOVE "${QGIS_APP_LIST_FILE}") file(TOUCH "${QGIS_APP_LIST_FILE}") set(QGIS_APP_DESCRIPTOR_PATH "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description") + foreach(app_name ${app_names}) add_dependencies(otbQgisDescriptor otbapp_${app_name}) file(APPEND "${QGIS_APP_LIST_FILE}" "${app_name}\n") - set(dfile "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description/${app_name}.txt") - # add_custom_command(OUTPUT "${dfile}" - # COMMAND ${generate_descriptor_cmd} - # "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" - # WORKING_DIRECTORY ${OTB_BINARY_DIR} - # COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/" - # VERBATIM) endforeach() # call otbQgisDescriptor with the app list, the location to create the files -# add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt" -# COMMAND ${generate_descriptor_cmd} -# "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps.txt" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" -# WORKING_DIRECTORY ${OTB_BINARY_DIR} -# COMMENT "Generate gqis descriptor files" -# VERBATIM) - +add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt" + COMMAND ${generate_descriptor_cmd} + "${QGIS_APP_LIST_FILE}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/" + WORKING_DIRECTORY ${OTB_BINARY_DIR} + COMMENT "Generate gqis descriptor files" + VERBATIM) -# add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt") -# add_dependencies(${otb-module}-all generate_descriptors) +add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt") +add_dependencies(${otb-module}-all generate_descriptors) diff --git a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx index 1579bd317e..1baaa8b303 100644 --- a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx +++ b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx @@ -33,43 +33,6 @@ #define QGIS_DEBUG 0 using namespace otb::Wrapper; -static const std::unordered_map<ParameterType, std::string> parameterTypeToString { - {ParameterType_Bool, "QgsProcessingParameterBoolean"}, - {ParameterType_Int, "QgsProcessingParameterNumber"}, - {ParameterType_Float, "QgsProcessingParameterNumber"}, - {ParameterType_Double, "QgsProcessingParameterNumber"}, - {ParameterType_RAM, "QgsProcessingParameterNumber"}, - {ParameterType_Radius, "QgsProcessingParameterNumber"}, - {ParameterType_Choice, "OTBParameterChoice"}, - {ParameterType_String, "QgsProcessingParameterString"}, - {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"}, - {ParameterType_InputFilename, "QgsProcessingParameterFile"}, - {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"}, - {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"}, - {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"}, - {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"}, - {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"}, - {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"}, - {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"}, - {ParameterType_Directory, "QgsProcessingParameterFile"}, - {ParameterType_Field, "QgsProcessingParameterField"}, - {ParameterType_Band, "QgsProcessingParameterBand"}, - {ParameterType_StringList, "QgsProcessingParameterString"}, - {ParameterType_ListView, "QgsProcessingParameterEnum"} -}; - -bool addModuleToAlgorithmList(std::ofstream& algs_list_file, - const std::string& module, - const std::string& group) -{ - if (algs_list_file) { - algs_list_file << group << "|" << module << std::endl; - return algs_list_file.good(); - } - else { - return false; - } -} std::string getMandatoryOutputImageParam(const Application::Pointer app, const std::vector<std::string>& app_keys) { @@ -87,6 +50,39 @@ std::string getMandatoryOutputImageParam(const Application::Pointer app, std::string QGISLineFactory(const Application::Pointer app, const std::string& paramKey) { + static const std::unordered_map<ParameterType, std::string> parameterTypeToString { + {ParameterType_Bool, "QgsProcessingParameterBoolean"}, + {ParameterType_Int, "QgsProcessingParameterNumber"}, + {ParameterType_Float, "QgsProcessingParameterNumber"}, + {ParameterType_Double, "QgsProcessingParameterNumber"}, + {ParameterType_RAM, "QgsProcessingParameterNumber"}, + {ParameterType_Radius, "QgsProcessingParameterNumber"}, + {ParameterType_Choice, "OTBParameterChoice"}, + {ParameterType_String, "QgsProcessingParameterString"}, + {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"}, + {ParameterType_InputFilename, "QgsProcessingParameterFile"}, + {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"}, + {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"}, + {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"}, + {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"}, + {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"}, + {ParameterType_Directory, "QgsProcessingParameterFile"}, + {ParameterType_Field, "QgsProcessingParameterField"}, + {ParameterType_Band, "QgsProcessingParameterBand"}, + {ParameterType_StringList, "QgsProcessingParameterString"}, + {ParameterType_ListView, "QgsProcessingParameterEnum"} + }; + + enum FilterType { + NONE = 0b00000000, + STRING = 0b00000001, + NUMERIC = 0b00000010, + MIXED = 0b00000011 + }; + + std::ostringstream qgis_line; const Parameter::Pointer param (app->GetParameterByKey(paramKey)); ParameterType type (param->GetType()); @@ -135,20 +131,26 @@ std::string QGISLineFactory(const Application::Pointer app, bool isEpsgCode = false; // use QgsProcessingParameterCrs if required. - // CHeck if paramKey ==epsg only or epsg is one onf the extension - const std::regex epsg_regex("*\.epsg\.*", std::regex_constants::grep); - std::cmatch tmp_match; // useless param just for function - if (paramKey == "epsg" || std::regex_match(paramKey, tmp_match, epsg_regex)) - { + // Check if paramKey == "epsg" only or epsg is one of the extensions + // use double \ to get one in final expression + const std::regex epsg_regex(".+\\.epsg\\..+"); + if (paramKey == "epsg" || std::regex_match(paramKey, epsg_regex)) { qgis_type = "QgsProcessingParameterCrs"; isEpsgCode = true; } qgis_line << qgis_type << "|" << paramKey << "|" << description; - std::vector<std::string> name_list; - std::vector<std::string> key_list; + int filterType; + std::vector<std::string> name_list; + std::vector<std::string> key_list; std::string values; + std::string proc_param_field; + ListViewParameter *lv_param; + ChoiceParameter* choiceParam; + FieldParameter* fieldParam; + BandParameter* bandParam; + switch (type) { case ParameterType_Int: @@ -220,7 +222,7 @@ std::string QGISLineFactory(const Application::Pointer app, default_value = "None|None"; break; case ParameterType_ListView: - ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer()); + lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer()); name_list = app->GetChoiceNames(paramKey); for( auto k : name_list) values += k + ";"; @@ -240,36 +242,36 @@ std::string QGISLineFactory(const Application::Pointer app, values.pop_back(); qgis_line << "|" << values; - ChoiceParameter* choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer()); + choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer()); default_value = std::to_string(choiceParam->GetValue()); break; case ParameterType_Field: - FieldParameter* fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer()); + fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer()); + filterType = FilterType::NONE; - enum { - STRING = 0b00000001, - NUMERIC = 0b00000010 - }; - - int filterType = 0b00000000; for (auto type : fieldParam->GetTypeFilter()) { if (type == OFTString) - filterType |= STRING; + filterType |= FilterType::STRING; else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal) - filterType |= NUMERIC; + filterType |= FilterType::NUMERIC; } + if (filterType == FilterType::NONE || filterType == FilterType::MIXED) + proc_param_field = "QgsProcessingParameterField.Any"; + else if (filterType == FilterType::STRING) + proc_param_field = "QgsProcessingParameterField.String"; + else if (filterType == FilterType::NUMERIC) + proc_param_field = "QgsProcessingParameterField.Numeric"; + qgis_line << "|None|" << fieldParam->GetVectorData() - << "|" << (filterType == STRING ? "QgsProcessingParameterField.String" : - filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" : - "QgsProcessingParameterField.Any") + << "|" << proc_param_field << "|" << (fieldParam->GetSingleSelection() ? "False" : "True") << "|" << optional; break; case ParameterType_Band: - BandParameter* bandParam = dynamic_cast<BandParameter*>(param.GetPointer()); + bandParam = dynamic_cast<BandParameter*>(param.GetPointer()); qgis_line << "|None|" << bandParam->GetRasterData() << "|" @@ -300,11 +302,13 @@ std::string QGISLineFactory(const Application::Pointer app, return qgis_line.str(); } -bool writeModuleQGISDescriptor(const Application::Pointer app, +void writeModuleQGISDescriptor(const Application::Pointer app, const std::string& module_name, const std::string& group, const std::string& descriptor_file_path) { // RAII capsule to automatically close file when we are out of scope + // as this function is able to throw, we must ensure that the file is + // correctly closed auto module_descriptor = std::unique_ptr<std::ofstream, std::function<void(std::ofstream*)>>( new std::ofstream(descriptor_file_path, std::ios::out), [](std::ofstream* o) { o->close(); } @@ -347,6 +351,8 @@ bool writeModuleQGISDescriptor(const Application::Pointer app, *module_descriptor << param_line << std::endl; } + // if there is a mandatory output, add a parameter to specify the type of + // output pixels if (!mandatory_out_param_name.empty()) { *module_descriptor << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl; @@ -356,43 +362,67 @@ bool writeModuleQGISDescriptor(const Application::Pointer app, int main(int argc, char* argv[]) { + // ------- Args parsing -------- if (argc < 3) { - std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl; + std::cerr << "Usage : " << argv[0] << " module_list_file OTB_APPLICATION_PATH [out_dir]" << std::endl; return EXIT_FAILURE; } - const std::string module(argv[1]); - + const std::string module_list_path(argv[1]); ApplicationRegistry::AddApplicationPath(argv[2]); - Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module); - - assert(!appli.IsNull()); - - std::string output_file = module + ".txt"; - std::string algs_txt = "algs.txt"; + std::string work_dir(""); + std::string output_file = module_list_path + ".txt"; if (argc > 3) { - output_file = std::string(argv[3]) + module + ".txt"; - algs_txt = std::string(argv[3]) + "algs.txt"; + work_dir = std::string(argv[3]) + "/"; } + // ------------------------ + + // get the list of all modules to generate their descriptors + std::ifstream module_list_file(module_list_path, std::ios_base::in); + const std::string algs_txt = work_dir + "algs.txt"; + + // get the algs_txt that contains modules and their groups + std::ofstream algs_list; + algs_list.open(algs_txt, std::ios::out | std::ios::app); + + std::string module_name; + + // for all modules list, write the detailled QGIS descriptor and add it + // in the algs.txt list with its group + while (std::getline(module_list_file, module_name)) { + bool descriptor_written = false; + Application::Pointer appli = ApplicationRegistry:: + CreateApplicationFaster(module_name); + assert(!appli.IsNull()); + const std::string group = appli->GetDocTags().size() > 0 ? + appli->GetDocTags()[0] : ""; + assert(!group.empty()); + + std::string qgis_desc_path = work_dir + module_name + ".txt"; + + // write detailled module desc + try { + writeModuleQGISDescriptor(appli, module_name, group, qgis_desc_path); + descriptor_written = true; + } + catch (const std::runtime_error& e) { + std::cerr << "Error while writing " << qgis_desc_path << ": " << e.what() + << std::endl; + } - const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : ""; - assert(!group.empty()); - - // write module - writeModuleQGISDescriptor(appli, module, group, output_file); - - std::cerr << "Writing " << output_file << std::endl; + // add it in list + if (descriptor_written) { + algs_list << group << "|" << module_name << std::endl; + } - std::ofstream indexFile; - indexFile.open(algs_txt, std::ios::out | std::ios::app); - indexFile << group << "|" << module << std::endl; - indexFile.close(); - std::cerr << "Updated " << algs_txt << std::endl; + appli = nullptr; + } - appli = nullptr; + algs_list.close(); + module_list_file.close(); ApplicationRegistry::CleanRegistry(); return EXIT_SUCCESS; -- GitLab