diff --git a/.github/workflows/buildAndDocumentation.yml b/.github/workflows/buildAndDocumentation.yml index 79dffdbf..69225ce6 100644 --- a/.github/workflows/buildAndDocumentation.yml +++ b/.github/workflows/buildAndDocumentation.yml @@ -12,7 +12,7 @@ env: CONFIG_GLOBAL: CONFIG_LINUX: -DWITH_MAGICK=true -DWITH_GMP=true -DWITH_FFTW3=true -DWARNING_AS_ERROR=ON -DWITH_HDF5=true -DWITH_QGLVIEWER=true -DWITH_CAIRO=true -DWITH_EIGEN=true -DDGTAL_ENABLE_FLOATING_POINT_EXCEPTIONS=true CONFIG_MAC: -DWITH_EIGEN=true -DWITH_GMP=tue - CONFIG_WINDOWS: -DWITH_OPENMP=true -DENABLE_CONAN=true #-DWITH_FFTW3=true #-DWITH_CAIRO=true #-DWITH_ITK=true -DWITH_GMP=true + CONFIG_WINDOWS: -DWITH_OPENMP=true -DENABLE_CONAN=true -DDISABLE_POLYSCOPE=ON #-DWITH_FFTW3=true #-DWITH_CAIRO=true #-DWITH_ITK=true -DWITH_GMP=true jobs: @@ -33,7 +33,7 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install zsh libqglviewer-dev-qt5 libboost-dev libeigen3-dev ninja-build libhdf5-serial-dev libboost-dev libcairo2-dev libgmp-dev libgraphicsmagick++1-dev libfftw3-dev + sudo apt-get install zsh libqglviewer-dev-qt5 libboost-dev libeigen3-dev ninja-build libhdf5-serial-dev libboost-dev libcairo2-dev libgmp-dev libgraphicsmagick++1-dev libfftw3-dev xorg-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev - name: Get white list tools run : | @@ -142,7 +142,7 @@ jobs: - name: Set up run: | sudo apt-get update - sudo apt-get install libboost-dev graphviz texlive doxygen libqglviewer-dev-qt5 libeigen3-dev ninja-build libhdf5-serial-dev libboost-dev libcairo2-dev libgmp-dev libgraphicsmagick++1-dev libfftw3-dev + sudo apt-get install libboost-dev graphviz texlive doxygen libqglviewer-dev-qt5 libeigen3-dev ninja-build libhdf5-serial-dev libboost-dev libcairo2-dev libgmp-dev libgraphicsmagick++1-dev libfftw3-dev xorg-dev libglu1-mesa-dev freeglut3-dev - name: DGtalBuild (linux) shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index 486c2139..1ab3a3cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") SET(DGTALTOOLS_RANDOMIZED_BUILD_THRESHOLD "100" CACHE INTERNAL "Threshold for the random selection of tools to build.") SET(DGTALTOOLS_RANDOMIZED_BUILD_WHITELIST "" CACHE INTERNAL "List of whitelisted tools to build.") option(NO_ADD_STBIMAGE_IMPLEMENT "To avoid duplicated linking errors (like LNK2005 in MSVC)" OFF) +option(DISABLE_POLYSCOPE "Disable polyscope based tools" OFF) @@ -106,6 +107,20 @@ if ( WITH_ITK ) ENDIF( WITH_ITK ) + +#----------------------------------------------------------------------------- +# polyscope +#----------------------------------------------------------------------------- +if (NOT(DISABLE_POLYSCOPE)) + message(STATUS "Enabling polyscope tools") + include(CPM) + include(polyscope) +else() + message(STATUS "Disabling polyscope tools") +endif() + + + # ----------------------------------------------------------------------------- # Documentation # ----------------------------------------------------------------------------- diff --git a/ChangeLog.md b/ChangeLog.md index 715386a9..add30418 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,11 @@ # DGtalTools 1.4 (beta) +- *build* + - Remove STBimage preprocessor instruction used to fix MVSC that is + no more usefull since DGtal PR [175](https://github.com/DGtal-team/DGtal/pull/1715) + (Bertrand Kerautret [#459](https://github.com/DGtal-team/DGtalTools/pull/459)) + + - *visualisation* - meshViewer: new options to change the default background color, to load camera settings at startup, to change at startup the light @@ -13,9 +19,22 @@ - meshViewer: new option to set alpha channel of the mesh color. (Bertrand Kerautret [#451](https://github.com/DGtal-team/DGtalTools/pull/451)) + - 3dSDPViewer: new option to set alpha channel of the mesh color. + (Xun Gong + [#452](https://github.com/DGtal-team/DGtalTools/pull/452)) + - meshViewer: Add colored SDP option in meshViewer when input texts is an alpha mesh and a colored SDP respectively. + (Xun Gong + [#452](https://github.com/DGtal-team/DGtalTools/pull/452)) + - volscope new vol visualization tool using polyscope (David Coeurjolly, + [#455](https://github.com/DGtal-team/DGtalTools/pull/455)) + - volscope documentation enhanced (David Coeurjolly, + [#460](https://github.com/DGtal-team/DGtalTools/pull/460)) + - *volumetric* - volReSample: fix the impossibility to export to vol when ITK is activated (Bertrand Kerautret [#445](https://github.com/DGtal-team/DGtalTools/pull/445)) + - volFillInterior: add new option to set the filling value. + (Bertrand Kerautret [#456](https://github.com/DGtal-team/DGtalTools/pull/456)) - *converters* - mesh2vol: small fix to read generic mesh. diff --git a/README.md b/README.md index 24f7ea25..5fd95a75 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,13 @@ Galleries 3dCompSurfelData 3dImplicitSurfaceExtractorByThickening + + + + + + volscope + diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 00000000..70aebf10 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,1154 @@ +# CPM.cmake - CMake's missing package manager +# =========================================== +# See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. +# +# MIT License +# ----------- +#[[ + Copyright (c) 2019-2022 Lars Melchior and contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +]] + +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# Initialize logging prefix +if(NOT CPM_INDENT) + set(CPM_INDENT + "CPM:" + CACHE INTERNAL "" + ) +endif() + +if(NOT COMMAND cpm_message) + function(cpm_message) + message(${ARGV}) + endfunction() +endif() + +set(CURRENT_CPM_VERSION 0.38.1) + +get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) +if(CPM_DIRECTORY) + if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) + if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) + message( + AUTHOR_WARNING + "${CPM_INDENT} \ +A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ +It is recommended to upgrade CPM to the most recent version. \ +See https://github.com/cpm-cmake/CPM.cmake for more information." + ) + endif() + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + include(FetchContent) + endif() + return() + endif() + + get_property( + CPM_INITIALIZED GLOBAL "" + PROPERTY CPM_INITIALIZED + SET + ) + if(CPM_INITIALIZED) + return() + endif() +endif() + +if(CURRENT_CPM_VERSION MATCHES "development-version") + message( + WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ +Please update to a recent release if possible. \ +See https://github.com/cpm-cmake/CPM.cmake for details." + ) +endif() + +set_property(GLOBAL PROPERTY CPM_INITIALIZED true) + +macro(cpm_set_policies) + # the policy allows us to change options without caching + cmake_policy(SET CMP0077 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + + # the policy allows us to change set(CACHE) without caching + if(POLICY CMP0126) + cmake_policy(SET CMP0126 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + endif() + + # The policy uses the download time for timestamp, instead of the timestamp in the archive. This + # allows for proper rebuilds when a projects url changes + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) + endif() +endmacro() +cpm_set_policies() + +option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" + $ENV{CPM_USE_LOCAL_PACKAGES} +) +option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" + $ENV{CPM_LOCAL_PACKAGES_ONLY} +) +option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) +option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" + $ENV{CPM_DONT_UPDATE_MODULE_PATH} +) +option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" + $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} +) +option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK + "Add all packages added through CPM.cmake to the package lock" + $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} +) +option(CPM_USE_NAMED_CACHE_DIRECTORIES + "Use additional directory of package name in cache on the most nested level." + $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} +) + +set(CPM_VERSION + ${CURRENT_CPM_VERSION} + CACHE INTERNAL "" +) +set(CPM_DIRECTORY + ${CPM_CURRENT_DIRECTORY} + CACHE INTERNAL "" +) +set(CPM_FILE + ${CMAKE_CURRENT_LIST_FILE} + CACHE INTERNAL "" +) +set(CPM_PACKAGES + "" + CACHE INTERNAL "" +) +set(CPM_DRY_RUN + OFF + CACHE INTERNAL "Don't download or configure dependencies (for testing)" +) + +if(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) +else() + set(CPM_SOURCE_CACHE_DEFAULT OFF) +endif() + +set(CPM_SOURCE_CACHE + ${CPM_SOURCE_CACHE_DEFAULT} + CACHE PATH "Directory to download CPM dependencies" +) + +if(NOT CPM_DONT_UPDATE_MODULE_PATH) + set(CPM_MODULE_PATH + "${CMAKE_BINARY_DIR}/CPM_modules" + CACHE INTERNAL "" + ) + # remove old modules + file(REMOVE_RECURSE ${CPM_MODULE_PATH}) + file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) + # locally added CPM modules should override global packages + set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") +endif() + +if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + set(CPM_PACKAGE_LOCK_FILE + "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" + CACHE INTERNAL "" + ) + file(WRITE ${CPM_PACKAGE_LOCK_FILE} + "# CPM Package Lock\n# This file should be committed to version control\n\n" + ) +endif() + +include(FetchContent) + +# Try to infer package name from git repository uri (path or url) +function(cpm_package_name_from_git_uri URI RESULT) + if("${URI}" MATCHES "([^/:]+)/?.git/?$") + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + else() + unset(${RESULT} PARENT_SCOPE) + endif() +endfunction() + +# Try to infer package name and version from a url +function(cpm_package_name_and_ver_from_url url outName outVer) + if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") + # We matched an archive + set(filename "${CMAKE_MATCH_1}") + + if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") + # We matched - (ie foo-1.2.3) + set(${outName} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + set(${outVer} + "${CMAKE_MATCH_2}" + PARENT_SCOPE + ) + elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") + # We couldn't find a name, but we found a version + # + # In many cases (which we don't handle here) the url would look something like + # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly + # distinguish the package name from the irrelevant bits. Moreover if we try to match the + # package name from the filename, we'd get bogus at best. + unset(${outName} PARENT_SCOPE) + set(${outVer} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + else() + # Boldly assume that the file name is the package name. + # + # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but + # such cases should be quite rare. No popular service does this... we think. + set(${outName} + "${filename}" + PARENT_SCOPE + ) + unset(${outVer} PARENT_SCOPE) + endif() + else() + # No ideas yet what to do with non-archives + unset(${outName} PARENT_SCOPE) + unset(${outVer} PARENT_SCOPE) + endif() +endfunction() + +function(cpm_find_package NAME VERSION) + string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") + find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) + if(${CPM_ARGS_NAME}_FOUND) + if(DEFINED ${CPM_ARGS_NAME}_VERSION) + set(VERSION ${${CPM_ARGS_NAME}_VERSION}) + endif() + cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") + CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") + set(CPM_PACKAGE_FOUND + YES + PARENT_SCOPE + ) + else() + set(CPM_PACKAGE_FOUND + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from +# finding the system library +function(cpm_create_module_file Name) + if(NOT CPM_DONT_UPDATE_MODULE_PATH) + # erase any previous modules + file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" + ) + endif() +endfunction() + +# Find a package locally or fallback to CPMAddPackage +function(CPMFindPackage) + set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + set(downloadPackage ${CPM_DOWNLOAD_ALL}) + if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) + set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + endif() + if(downloadPackage) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + if(CPM_PACKAGE_ALREADY_ADDED) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(NOT CPM_PACKAGE_FOUND) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + endif() + +endfunction() + +# checks if a package has been added before +function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) + if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) + CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) + if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") + message( + WARNING + "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." + ) + endif() + cpm_get_fetch_properties(${CPM_ARGS_NAME}) + set(${CPM_ARGS_NAME}_ADDED NO) + set(CPM_PACKAGE_ALREADY_ADDED + YES + PARENT_SCOPE + ) + cpm_export_variables(${CPM_ARGS_NAME}) + else() + set(CPM_PACKAGE_ALREADY_ADDED + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of +# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted +# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 +function(cpm_parse_add_package_single_arg arg outArgs) + # Look for a scheme + if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") + string(TOLOWER "${CMAKE_MATCH_1}" scheme) + set(uri "${CMAKE_MATCH_2}") + + # Check for CPM-specific schemes + if(scheme STREQUAL "gh") + set(out "GITHUB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "gl") + set(out "GITLAB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "bb") + set(out "BITBUCKET_REPOSITORY;${uri}") + set(packageType "git") + # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine + # type + elseif(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Fall back to a URL + set(out "URL;${arg}") + set(packageType "archive") + + # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. + # We just won't bother with the additional complexity it will induce in this function. SVN is + # done by multi-arg + endif() + else() + if(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Give up + message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") + endif() + endif() + + # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs + # containing '@' can be used + string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") + + # Parse the rest according to package type + if(packageType STREQUAL "git") + # For git repos we interpret #... as a tag or branch or commit hash + string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") + elseif(packageType STREQUAL "archive") + # For archives we interpret #... as a URL hash. + string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") + # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url + # should do this at a later point + else() + # We should never get here. This is an assertion and hitting it means there's a bug in the code + # above. A packageType was set, but not handled by this if-else. + message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") + endif() + + set(${outArgs} + ${out} + PARENT_SCOPE + ) +endfunction() + +# Check that the working directory for a git repo is clean +function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) + + find_package(Git REQUIRED) + + if(NOT GIT_EXECUTABLE) + # No git executable, assume directory is clean + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + # check for uncommitted changes + execute_process( + COMMAND ${GIT_EXECUTABLE} status --porcelain + RESULT_VARIABLE resultGitStatus + OUTPUT_VARIABLE repoStatus + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET + WORKING_DIRECTORY ${repoPath} + ) + if(resultGitStatus) + # not supposed to happen, assume clean anyway + message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + if(NOT "${repoStatus}" STREQUAL "") + set(${isClean} + FALSE + PARENT_SCOPE + ) + return() + endif() + + # check for committed changes + execute_process( + COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} + RESULT_VARIABLE resultGitDiff + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET + WORKING_DIRECTORY ${repoPath} + ) + + if(${resultGitDiff} EQUAL 0) + set(${isClean} + TRUE + PARENT_SCOPE + ) + else() + set(${isClean} + FALSE + PARENT_SCOPE + ) + endif() + +endfunction() + +# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload +# FetchContent calls. As these are internal cmake properties, this method should be used carefully +# and may need modification in future CMake versions. Source: +# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 +function(cpm_override_fetchcontent contentName) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") + if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + string(TOLOWER ${contentName} contentNameLower) + set(prefix "_FetchContent_${contentNameLower}") + + set(propertyName "${prefix}_sourceDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") + + set(propertyName "${prefix}_binaryDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") + + set(propertyName "${prefix}_populated") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} TRUE) +endfunction() + +# Download and add a package from source +function(CPMAddPackage) + cpm_set_policies() + + list(LENGTH ARGN argnLength) + if(argnLength EQUAL 1) + cpm_parse_add_package_single_arg("${ARGN}" ARGN) + + # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM + set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") + endif() + + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + BITBUCKET_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + DOWNLOAD_COMMAND + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR + ) + + set(multiValueArgs URL OPTIONS) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") + + # Set default values for arguments + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + if(CPM_ARGS_DOWNLOAD_ONLY) + set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) + else() + set(DOWNLOAD_ONLY NO) + endif() + + if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") + endif() + + if(DEFINED CPM_ARGS_GIT_REPOSITORY) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) + if(NOT DEFINED CPM_ARGS_GIT_TAG) + set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) + endif() + + # If a name wasn't provided, try to infer it from the git repo + if(NOT DEFINED CPM_ARGS_NAME) + cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) + endif() + endif() + + set(CPM_SKIP_FETCH FALSE) + + if(DEFINED CPM_ARGS_GIT_TAG) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) + # If GIT_SHALLOW is explicitly specified, honor the value. + if(DEFINED CPM_ARGS_GIT_SHALLOW) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) + endif() + endif() + + if(DEFINED CPM_ARGS_URL) + # If a name or version aren't provided, try to infer them from the URL + list(GET CPM_ARGS_URL 0 firstUrl) + cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) + # If we fail to obtain name and version from the first URL, we could try other URLs if any. + # However multiple URLs are expected to be quite rare, so for now we won't bother. + + # If the caller provided their own name and version, they trump the inferred ones. + if(NOT DEFINED CPM_ARGS_NAME) + set(CPM_ARGS_NAME ${nameFromUrl}) + endif() + if(NOT DEFINED CPM_ARGS_VERSION) + set(CPM_ARGS_VERSION ${verFromUrl}) + endif() + + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") + endif() + + # Check for required arguments + + if(NOT DEFINED CPM_ARGS_NAME) + message( + FATAL_ERROR + "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" + ) + endif() + + # Check if package has been added before + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + if(CPM_PACKAGE_ALREADY_ADDED) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for manual overrides + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") + set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) + set(CPM_${CPM_ARGS_NAME}_SOURCE "") + CPMAddPackage( + NAME "${CPM_ARGS_NAME}" + SOURCE_DIR "${PACKAGE_SOURCE}" + EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" + SYSTEM "${CPM_ARGS_SYSTEM}" + OPTIONS "${CPM_ARGS_OPTIONS}" + SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" + DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" + FORCE True + ) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for available declaration + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") + set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) + set(CPM_DECLARATION_${CPM_ARGS_NAME} "") + CPMAddPackage(${declaration}) + cpm_export_variables(${CPM_ARGS_NAME}) + # checking again to ensure version and option compatibility + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + return() + endif() + + if(NOT CPM_ARGS_FORCE) + if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(CPM_PACKAGE_FOUND) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + if(CPM_LOCAL_PACKAGES_ONLY) + message( + SEND_ERROR + "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" + ) + endif() + endif() + endif() + + CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") + + if(DEFINED CPM_ARGS_GIT_TAG) + set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") + else() + set(PACKAGE_INFO "${CPM_ARGS_VERSION}") + endif() + + if(DEFINED FETCHCONTENT_BASE_DIR) + # respect user's FETCHCONTENT_BASE_DIR if set + set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) + else() + set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) + endif() + + if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) + if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) + # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work + # for relative paths. + get_filename_component( + source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} + ) + else() + set(source_directory ${CPM_ARGS_SOURCE_DIR}) + endif() + if(NOT EXISTS ${source_directory}) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") + endif() + elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) + list(SORT origin_parameters) + if(CPM_USE_NAMED_CACHE_DIRECTORIES) + string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) + else() + string(SHA1 origin_hash "${origin_parameters}") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) + endif() + # Expand `download_directory` relative path. This is important because EXISTS doesn't work for + # relative paths. + get_filename_component(download_directory ${download_directory} ABSOLUTE) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) + + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock) + endif() + + if(EXISTS ${download_directory}) + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} "${download_directory}" + "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + ) + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + + if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) + # warn if cache has been changed since checkout + cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) + if(NOT ${IS_CLEAN}) + message( + WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" + ) + endif() + endif() + + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") + + # As the source dir is already cached/populated, we override the call to FetchContent. + set(CPM_SKIP_FETCH TRUE) + cpm_override_fetchcontent( + "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" + ) + + else() + # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but + # it should guarantee no commit hash get mis-detected. + if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) + cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) + if(NOT ${IS_HASH}) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) + endif() + endif() + + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) + set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") + endif() + endif() + + cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + + if(CPM_PACKAGE_LOCK_ENABLED) + if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) + cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + elseif(CPM_ARGS_SOURCE_DIR) + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") + else() + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + endif() + endif() + + cpm_message( + STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" + ) + + if(NOT CPM_SKIP_FETCH) + cpm_declare_fetch( + "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" + ) + cpm_fetch_package("${CPM_ARGS_NAME}" populated) + if(CPM_CACHE_SOURCE AND download_directory) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + if(${populated}) + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + endif() + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + endif() + + set(${CPM_ARGS_NAME}_ADDED YES) + cpm_export_variables("${CPM_ARGS_NAME}") +endfunction() + +# Fetch a previously declared package +macro(CPMGetPackage Name) + if(DEFINED "CPM_DECLARATION_${Name}") + CPMAddPackage(NAME ${Name}) + else() + message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") + endif() +endmacro() + +# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set +macro(cpm_export_variables name) + set(${name}_SOURCE_DIR + "${${name}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${name}_BINARY_DIR + "${${name}_BINARY_DIR}" + PARENT_SCOPE + ) + set(${name}_ADDED + "${${name}_ADDED}" + PARENT_SCOPE + ) + set(CPM_LAST_PACKAGE_NAME + "${name}" + PARENT_SCOPE + ) +endmacro() + +# declares a package, so that any call to CPMAddPackage for the package name will use these +# arguments instead. Previous declarations will not be overridden. +macro(CPMDeclarePackage Name) + if(NOT DEFINED "CPM_DECLARATION_${Name}") + set("CPM_DECLARATION_${Name}" "${ARGN}") + endif() +endmacro() + +function(cpm_add_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") + endif() +endfunction() + +function(cpm_add_comment_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} + "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" + ) + endif() +endfunction() + +# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to +# update it +macro(CPMUsePackageLock file) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) + if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + endif() + if(NOT TARGET cpm-update-package-lock) + add_custom_target( + cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} + ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} + ) + endif() + set(CPM_PACKAGE_LOCK_ENABLED true) + endif() +endmacro() + +# registers a package that has been added to CPM +function(CPMRegisterPackage PACKAGE VERSION) + list(APPEND CPM_PACKAGES ${PACKAGE}) + set(CPM_PACKAGES + ${CPM_PACKAGES} + CACHE INTERNAL "" + ) + set("CPM_PACKAGE_${PACKAGE}_VERSION" + ${VERSION} + CACHE INTERNAL "" + ) +endfunction() + +# retrieve the current version of the package to ${OUTPUT} +function(CPMGetPackageVersion PACKAGE OUTPUT) + set(${OUTPUT} + "${CPM_PACKAGE_${PACKAGE}_VERSION}" + PARENT_SCOPE + ) +endfunction() + +# declares a package in FetchContent_Declare +function(cpm_declare_fetch PACKAGE VERSION INFO) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") + return() + endif() + + FetchContent_Declare(${PACKAGE} ${ARGN}) +endfunction() + +# returns properties for a package previously defined by cpm_declare_fetch +function(cpm_get_fetch_properties PACKAGE) + if(${CPM_DRY_RUN}) + return() + endif() + + set(${PACKAGE}_SOURCE_DIR + "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" + PARENT_SCOPE + ) +endfunction() + +function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) + if(${CPM_DRY_RUN}) + return() + endif() + + set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR + "${source_dir}" + CACHE INTERNAL "" + ) + set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR + "${binary_dir}" + CACHE INTERNAL "" + ) +endfunction() + +# adds a package as a subdirectory if viable, according to provided options +function( + cpm_add_subdirectory + PACKAGE + DOWNLOAD_ONLY + SOURCE_DIR + BINARY_DIR + EXCLUDE + SYSTEM + OPTIONS +) + + if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) + set(addSubdirectoryExtraArgs "") + if(EXCLUDE) + list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) + endif() + if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") + # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM + list(APPEND addSubdirectoryExtraArgs SYSTEM) + endif() + if(OPTIONS) + foreach(OPTION ${OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach() + endif() + set(CPM_OLD_INDENT "${CPM_INDENT}") + set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") + add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) + set(CPM_INDENT "${CPM_OLD_INDENT}") + endif() +endfunction() + +# downloads a previously declared package via FetchContent and exports the variables +# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope +function(cpm_fetch_package PACKAGE populated) + set(${populated} + FALSE + PARENT_SCOPE + ) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") + return() + endif() + + FetchContent_GetProperties(${PACKAGE}) + + string(TOLOWER "${PACKAGE}" lower_case_name) + + if(NOT ${lower_case_name}_POPULATED) + FetchContent_Populate(${PACKAGE}) + set(${populated} + TRUE + PARENT_SCOPE + ) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} + ) + + set(${PACKAGE}_SOURCE_DIR + ${${lower_case_name}_SOURCE_DIR} + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + ${${lower_case_name}_BINARY_DIR} + PARENT_SCOPE + ) +endfunction() + +# splits a package option +function(cpm_parse_option OPTION) + string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") + string(LENGTH "${OPTION}" OPTION_LENGTH) + string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) + if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) + # no value for key provided, assume user wants to set option to "ON" + set(OPTION_VALUE "ON") + else() + math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") + string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) + endif() + set(OPTION_KEY + "${OPTION_KEY}" + PARENT_SCOPE + ) + set(OPTION_VALUE + "${OPTION_VALUE}" + PARENT_SCOPE + ) +endfunction() + +# guesses the package version from a git tag +function(cpm_get_version_from_git_tag GIT_TAG RESULT) + string(LENGTH ${GIT_TAG} length) + if(length EQUAL 40) + # GIT_TAG is probably a git hash + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + endif() +endfunction() + +# guesses if the git tag is a commit hash or an actual tag or a branch name. +function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) + string(LENGTH "${GIT_TAG}" length) + # full hash has 40 characters, and short hash has at least 7 characters. + if(length LESS 7 OR length GREATER 40) + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") + set(${RESULT} + 1 + PARENT_SCOPE + ) + else() + set(${RESULT} + 0 + PARENT_SCOPE + ) + endif() + endif() +endfunction() + +function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + DOWNLOAD_COMMAND + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + ) + set(multiValueArgs OPTIONS) + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(oneArgName ${oneValueArgs}) + if(DEFINED CPM_ARGS_${oneArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + if(${oneArgName} STREQUAL "SOURCE_DIR") + string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} + ${CPM_ARGS_${oneArgName}} + ) + endif() + string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") + endif() + endforeach() + foreach(multiArgName ${multiValueArgs}) + if(DEFINED CPM_ARGS_${multiArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") + foreach(singleOption ${CPM_ARGS_${multiArgName}}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") + endforeach() + endif() + endforeach() + + if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ") + foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) + string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") + endforeach() + string(APPEND PRETTY_OUT_VAR "\n") + endif() + + set(${OUT_VAR} + ${PRETTY_OUT_VAR} + PARENT_SCOPE + ) + +endfunction() diff --git a/cmake/polyscope.cmake b/cmake/polyscope.cmake new file mode 100644 index 00000000..f95964c3 --- /dev/null +++ b/cmake/polyscope.cmake @@ -0,0 +1,14 @@ +if (TARGET polyscope) + return() +endif() + +set(CMAKE_CXX_FLAGS_DEBUG_OLD "${CMAKE_CXX_FLAGS_DEBUG}") +set(CMAKE_CXX_FLAGS_DEBUG "-w") + +CPMAddPackage( + NAME polyscope + VERSION 1.2.0 + GITHUB_REPOSITORY "nmwsharp/polyscope" +) + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG_OLD}") diff --git a/doc/images/volscope-surface.png b/doc/images/volscope-surface.png new file mode 100644 index 00000000..3c1c0c4b Binary files /dev/null and b/doc/images/volscope-surface.png differ diff --git a/doc/images/volscope-volumetric.png b/doc/images/volscope-volumetric.png new file mode 100644 index 00000000..f4987a9c Binary files /dev/null and b/doc/images/volscope-volumetric.png differ diff --git a/doc/visualization.dox b/doc/visualization.dox index e8f1b6c9..f458ab1c 100644 --- a/doc/visualization.dox +++ b/doc/visualization.dox @@ -18,7 +18,7 @@ @section visualization_Doc Visualization - + - @ref volscope : a new generic VOL file viewer using polyscope. - @ref Doc2dCompImage : compare images and displays differences (squared and absolute differences). - @ref CompSurfelData : computes generic scalar surfel data comparisons (squared error) ( given from an input data file and from a reference one). - @ref Doc3DCurvatureViewer : permits to compute and visualize mean or gaussian curvature of binary shapes. diff --git a/visualisation/3dSDPViewer.cpp b/visualisation/3dSDPViewer.cpp index a4d44537..845e2a52 100644 --- a/visualisation/3dSDPViewer.cpp +++ b/visualisation/3dSDPViewer.cpp @@ -45,8 +45,6 @@ #include "CLI11.hpp" - - using namespace std; using namespace DGtal; using namespace Z3i; @@ -55,13 +53,13 @@ typedef Viewer3D Viewer; /** @page Doc3DSDPViewer 3DSDPViewer - + @brief Displays a sequence of 3d discrete points by using QGLviewer. @b Usage: 3dSDPViewer [OPTIONS] 1 [f] [lineSize] [filterVectors] @b Allowed @b options @b are : - + @code Positionals: @@ -75,6 +73,7 @@ typedef Viewer3D Viewer; -l,--lineColor INT x 4 set the color of line: r g b a. -m,--addMesh TEXT append a mesh (off/obj) to the point set visualization. --customColorMesh UINT x 4 set the R, G, B, A components of the colors of the mesh faces (mesh added with option --addMesh). + --customAlphaMesh UINT set single alpha(A) components of the colors of the mesh faces (mesh added with option --addMesh). --importColors import point colors from the input file (R G B colors should be by default at index 3, 4, 5). --setColorsIndex UINT x 3 Needs: --importColors customize the index of the imported colors in the source file (used by --importColor). By default the initial indexes are 3, 4, 5. @@ -96,11 +95,11 @@ typedef Viewer3D Viewer; -u,--unitVector FLOAT=0 specifies that the SDP vector file format (of --drawVectors option) should be interpreted as unit vectors (each vector position is be defined from the input point (with input order) with a constant norm defined by [arg]). --filterVectors FLOAT=100 filters vector input file in order to display only the [arg] percent of the input vectors (uniformly selected, to be used with option --drawVectors else no effect). --interactiveDisplayVoxCoords by using this option the coordinates can be displayed after selection (shift+left click on voxel). - + @endcode - @b Basic @b example: + @b Basic @b example: You can display a set of 3D points with sphere primitive and lines: @code @@ -115,14 +114,14 @@ typedef Viewer3D Viewer; @b Example @b with @b interactive @b selection : This tool can be useful to recover coordinates from a set of voxels. To do it, you have to add the option allowing to activate the interactive selection (with --interactiveDisplayVoxCoords), for instance if you apply: -@code +@code $ 3dSDPViewer $DGtal/tests/samples/sinus3D.dat --interactiveDisplayVoxCoords - -@endcode + +@endcode you should be able to select a voxel by using the SHIFT key and by clicking on a voxel: @image html res3DSDPViewerInteractive.png " " - + @b Visualization @b of @b large @b point @b set If you need to display an important number of points, you can use the primitive @e glPoints instead @e voxel or @e sphere (-p glPoints). You will obtain such type of a visualization: @@ -136,245 +135,250 @@ If you need to display an important number of points, you can use the primitive */ - - // call back function to display voxel coordinates -int -displayCoordsCallBack( void* viewer, int name, void* data ) +int displayCoordsCallBack(void *viewer, int name, void *data) { - vector *vectVoxels = (vector *) data; + vector *vectVoxels = (vector *)data; std::stringstream ss; - ss << "Selected voxel: (" << (*vectVoxels)[name][0] << ", "; - ss << (*vectVoxels)[name][1] << ", "; - ss << (*vectVoxels)[name][2] << ") "; - ((Viewer *) viewer)->displayMessage(QString(ss.str().c_str()), 100000); - + ss << "Selected voxel: (" << (*vectVoxels)[name][0] << ", "; + ss << (*vectVoxels)[name][1] << ", "; + ss << (*vectVoxels)[name][2] << ") "; + ((Viewer *)viewer)->displayMessage(QString(ss.str().c_str()), 100000); + return 0; } - -int main( int argc, char** argv ) +int main(int argc, char **argv) { // parse command line using CLI ---------------------------------------------- CLI::App app; std::string inputFileName; - std::vector vectIndexSDP {0,1,2}; + std::vector vectIndexSDP{0, 1, 2}; Color lineColor(100, 100, 250); Color pointColor(250, 250, 250); std::vector vectColorPt; std::vector vectColorLine; std::string meshName; - std::string typePrimitive {"voxel"}; + std::string typePrimitive{"voxel"}; std::string vectorsFileName; std::vector vectSphereRadius; std::vector vectColMesh; - std::vector vectIndexColorImport {3,4,5}; - bool importColorLabels {false}; - bool noPointDisplay {false}; - bool drawLines {false}; - bool sphereRadiusFromInput {true}; - bool importColors {false}; - bool interactiveDisplayVoxCoords {false}; - float sx {1.0}; - float sy {1.0}; - float sz {1.0}; - unsigned int sphereResolution {30}; - double sphereRadius {0.2}; - double lineSize {0.2}; - double filterValue {100.0}; - double constantNorm {0.0}; - double percentageFilterVect {100.0}; + std::vector vectIndexColorImport{3, 4, 5}; + bool importColorLabels{false}; + bool noPointDisplay{false}; + bool drawLines{false}; + bool sphereRadiusFromInput{true}; + bool importColors{false}; + bool interactiveDisplayVoxCoords{false}; + float sx{1.0}; + float sy{1.0}; + float sz{1.0}; + unsigned int sphereResolution{30}; + double sphereRadius{0.2}; + double lineSize{0.2}; + double filterValue{100.0}; + double constantNorm{0.0}; + double percentageFilterVect{100.0}; + unsigned int customAlphaMesh; unsigned int colorLabelIndex = 3; - + app.description("Display sequence of 3d discrete points by using QGLviewer."); - app.add_option("-i,--input,1", inputFileName, "input file: sdp (sequence of discrete points)." ) - ->required() - ->check(CLI::ExistingFile); - app.add_option("--SDPindex",vectIndexSDP, "specify the sdp index (by default 0,1,2).") - ->expected(3); - app.add_option("--pointColor,-c",vectColorPt, "set the color of points: r g b a.") - ->expected(4); - app.add_option("--lineColor,-l",vectColorLine, "set the color of line: r g b a.") - ->expected(4); - app.add_option("--addMesh,-m",meshName, "append a mesh (off/obj) to the point set visualization."); - auto optMesh = app.add_option("--customColorMesh",vectColMesh, "set the R, G, B, A components of the colors of the mesh faces (mesh added with option --addMesh).") - ->expected(4); - auto importColOpt = app.add_flag("--importColors",importColors, "import point colors from the input file (R G B colors should be by default at index 3, 4, 5)."); - app.add_option("--setColorsIndex", vectIndexColorImport,"customize the index of the imported colors in the source file (used by --importColor). By default the initial indexes are 3, 4, 5.") - ->expected(3) - ->needs(importColOpt); - - app.add_flag("--importColorLabels", importColorLabels,"import color labels from the input file (label index should be by default at index 3)." ); + app.add_option("-i,--input,1", inputFileName, "input file: sdp (sequence of discrete points).") + ->required() + ->check(CLI::ExistingFile); + app.add_option("--SDPindex", vectIndexSDP, "specify the sdp index (by default 0,1,2).") + ->expected(3); + app.add_option("--pointColor,-c", vectColorPt, "set the color of points: r g b a.") + ->expected(4); + app.add_option("--lineColor,-l", vectColorLine, "set the color of line: r g b a.") + ->expected(4); + app.add_option("--addMesh,-m", meshName, "append a mesh (off/obj) to the point set visualization."); + app.add_option("--customAlphaMesh", customAlphaMesh, "set the alpha components of the colors of the mesh faces (can be applied for each mesh)."); + auto optMesh = app.add_option("--customColorMesh", vectColMesh, "set the R, G, B, A components of the colors of the mesh faces (mesh added with option --addMesh).") + ->expected(4); + auto importColOpt = app.add_flag("--importColors", importColors, "import point colors from the input file (R G B colors should be by default at index 3, 4, 5)."); + app.add_option("--setColorsIndex", vectIndexColorImport, "customize the index of the imported colors in the source file (used by --importColor). By default the initial indexes are 3, 4, 5.") + ->expected(3) + ->needs(importColOpt); + + app.add_flag("--importColorLabels", importColorLabels, "import color labels from the input file (label index should be by default at index 3)."); app.add_option("--setColorLabelIndex", colorLabelIndex, "customize the index of the imported color labels in the source file (used by -importColorLabels).", true); app.add_option("--filter,-f", filterValue, "filter input file in order to display only the [arg] percent of the input 3D points (uniformly selected).", true); - app.add_flag("--noPointDisplay",noPointDisplay, "usefull for instance to only display the lines between points." ); - app.add_flag("--drawLines", drawLines, "draw the line between discrete points." ); - app.add_option("--scaleX,-x", sx, "set the scale value in the X direction", true ); - app.add_option("--scaleY,-y", sy, "set the scale value in the Y direction", true ); - app.add_option("--scaleZ,-z", sy, "set the scale value in the Z direction", true ); - app.add_option("--sphereResolution",sphereResolution, "defines the sphere resolution (used when the primitive is set to the sphere).", true ); + app.add_flag("--noPointDisplay", noPointDisplay, "usefull for instance to only display the lines between points."); + app.add_flag("--drawLines", drawLines, "draw the line between discrete points."); + app.add_option("--scaleX,-x", sx, "set the scale value in the X direction", true); + app.add_option("--scaleY,-y", sy, "set the scale value in the Y direction", true); + app.add_option("--scaleZ,-z", sy, "set the scale value in the Z direction", true); + app.add_option("--sphereResolution", sphereResolution, "defines the sphere resolution (used when the primitive is set to the sphere).", true); app.add_option("-s,--sphereRadius", sphereRadius, "defines the sphere radius (used when the primitive is set to the sphere).", true); app.add_flag("--sphereRadiusFromInput", sphereRadiusFromInput, "takes, as sphere radius, the 4th field of the sdp input file."); app.add_option("--lineSize", lineSize, "defines the line size (used when the --drawLines or --drawVectors option is selected).", true); - app.add_option("--primitive,-p",typePrimitive, "set the primitive to display the set of points.", true ) - -> check(CLI::IsMember({"voxel", "glPoints", "sphere"})); - app.add_option("--drawVectors,-v",vectorsFileName, "SDP vector file: draw a set of vectors from the given file (each vector are determined by two consecutive point given, each point represented by its coordinates on a single line."); - - app.add_option("--unitVector,-u", constantNorm, "specifies that the SDP vector file format (of --drawVectors option) should be interpreted as unit vectors (each vector position is be defined from the input point (with input order) with a constant norm defined by [arg]).", true ); - - app.add_option("--filterVectors", percentageFilterVect, "filters vector input file in order to display only the [arg] percent of the input vectors (uniformly selected, to be used with option --drawVectors else no effect). ", true ); + app.add_option("--primitive,-p", typePrimitive, "set the primitive to display the set of points.", true) + ->check(CLI::IsMember({"voxel", "glPoints", "sphere"})); + app.add_option("--drawVectors,-v", vectorsFileName, "SDP vector file: draw a set of vectors from the given file (each vector are determined by two consecutive point given, each point represented by its coordinates on a single line."); + + app.add_option("--unitVector,-u", constantNorm, "specifies that the SDP vector file format (of --drawVectors option) should be interpreted as unit vectors (each vector position is be defined from the input point (with input order) with a constant norm defined by [arg]).", true); + + app.add_option("--filterVectors", percentageFilterVect, "filters vector input file in order to display only the [arg] percent of the input vectors (uniformly selected, to be used with option --drawVectors else no effect). ", true); app.add_flag("--interactiveDisplayVoxCoords", interactiveDisplayVoxCoords, " by using this option the coordinates can be displayed after selection (shift+left click on voxel)."); - - + app.get_formatter()->column_width(40); CLI11_PARSE(app, argc, argv); // END parse command line using CLI ---------------------------------------------- - - if (vectColorLine.size() == 4){ + if (vectColorLine.size() == 4) + { lineColor.setRGBi(vectColorLine[0], vectColorLine[1], vectColorLine[2], vectColorLine[3]); } - if (vectColorPt.size() == 4){ + if (vectColorPt.size() == 4) + { pointColor.setRGBi(vectColorPt[0], vectColorPt[1], vectColorPt[2], vectColorPt[3]); } - - QApplication application(argc,argv); - - + QApplication application(argc, argv); + bool useUnitVector = constantNorm != 0.0; - + typedef Viewer3D Viewer; Z3i::KSpace K; - Viewer viewer( K ); + Viewer viewer(K); viewer.setWindowTitle("3dSPD Viewer"); viewer.show(); viewer.setGLScale(sx, sy, sz); viewer.myGLLineMinWidth = lineSize; viewer << CustomColors3D(pointColor, pointColor); - - + // Get vector of colors if imported. std::vector vectColors; - if(importColors) + if (importColors) { - std::vector r = TableReader::getColumnElementsFromFile(inputFileName,vectIndexColorImport[0]); - std::vector g = TableReader::getColumnElementsFromFile(inputFileName,vectIndexColorImport[1]); - std::vector b = TableReader::getColumnElementsFromFile(inputFileName,vectIndexColorImport[2]); - for (unsigned int i = 0; i r = TableReader::getColumnElementsFromFile(inputFileName, vectIndexColorImport[0]); + std::vector g = TableReader::getColumnElementsFromFile(inputFileName, vectIndexColorImport[1]); + std::vector b = TableReader::getColumnElementsFromFile(inputFileName, vectIndexColorImport[2]); + for (unsigned int i = 0; i < r.size(); i++) + { vectColors.push_back(Color(r[i], g[i], b[i])); } } // Get vector of colors if imported. - std::vector< int> vectColorLabels; + std::vector vectColorLabels; unsigned int maxLabel = 1; - if(importColorLabels) + if (importColorLabels) { - vectColorLabels = TableReader< int>::getColumnElementsFromFile(inputFileName, colorLabelIndex); + vectColorLabels = TableReader::getColumnElementsFromFile(inputFileName, colorLabelIndex); maxLabel = *(std::max_element(vectColorLabels.begin(), vectColorLabels.end())); } HueShadeColorMap aColorMap(0, maxLabel); - - - if(sphereRadiusFromInput) + + if (sphereRadiusFromInput) { - vectSphereRadius = TableReader::getColumnElementsFromFile(inputFileName,3); + vectSphereRadius = TableReader::getColumnElementsFromFile(inputFileName, 3); } - + vector vectVoxels; vectVoxels = PointListReader::getPointsFromFile(inputFileName, vectIndexSDP); - + int name = 0; - if(!noPointDisplay){ + if (!noPointDisplay) + { if (typePrimitive == "glPoints") + { + viewer.setUseGLPointForBalls(true); + } + + int step = max(1, (int)(100 / filterValue)); + for (unsigned int i = 0; i < vectVoxels.size(); i = i + step) + { + if (importColors) { - viewer.setUseGLPointForBalls(true); + Color col = vectColors[i]; + viewer.setFillColor(col); + } + else if (importColorLabels) + { + unsigned int index = vectColorLabels[i]; + Color col = aColorMap(index); + viewer.setFillColor(col); } - int step = max(1, (int) (100/filterValue)); - for(unsigned int i=0;i< vectVoxels.size(); i=i+step){ - if(importColors) - { - Color col = vectColors[i]; - viewer.setFillColor(col); - } - else if(importColorLabels) - { - unsigned int index = vectColorLabels[i]; - Color col = aColorMap(index); - viewer.setFillColor(col); - } - - if(typePrimitive=="voxel" ){ + if (typePrimitive == "voxel") + { if (interactiveDisplayVoxCoords) { - viewer << SetName3D( name++ ) ; + viewer << SetName3D(name++); } viewer << Z3i::Point((int)vectVoxels.at(i)[0], (int)vectVoxels.at(i)[1], (int)vectVoxels.at(i)[2]); } else - { - viewer.addBall(vectVoxels.at(i), sphereRadius, sphereResolution); - } + { + viewer.addBall(vectVoxels.at(i), sphereRadius, sphereResolution); + } } - + viewer << CustomColors3D(lineColor, lineColor); - if(drawLines) + if (drawLines) { - for(unsigned int i=1;i< vectVoxels.size(); i++) + for (unsigned int i = 1; i < vectVoxels.size(); i++) { - viewer.addLine(vectVoxels.at(i-1), vectVoxels.at(i), lineSize); + viewer.addLine(vectVoxels.at(i - 1), vectVoxels.at(i), lineSize); } } - - if(vectorsFileName != "") + + if (vectorsFileName != "") { std::vector vectorsPt = PointListReader::getPointsFromFile(vectorsFileName); - if (vectorsPt.size()%2==1) + if (vectorsPt.size() % 2 == 1) { - trace.info()<<"Warning the two set of points doesn't contains the same number of points, some vectors will be skipped." << std::endl; + trace.info() << "Warning the two set of points doesn't contains the same number of points, some vectors will be skipped." << std::endl; } double percentage = percentageFilterVect; - int step = max(1, (int) (100/percentage)); - - if(useUnitVector) + int step = max(1, (int)(100 / percentage)); + + if (useUnitVector) { - for(unsigned int i =0; i< std::min(vectVoxels.size(), vectorsPt.size()); i=i+2*step) + for (unsigned int i = 0; i < std::min(vectVoxels.size(), vectorsPt.size()); i = i + 2 * step) { - viewer.addLine(vectVoxels.at(i), vectVoxels.at(i)+vectorsPt.at(i)*constantNorm, lineSize); + viewer.addLine(vectVoxels.at(i), vectVoxels.at(i) + vectorsPt.at(i) * constantNorm, lineSize); } } else { - for(unsigned int i =0; i mesh(!customColorMesh); - mesh << meshName ; - viewer << mesh; - } - if (interactiveDisplayVoxCoords) - { - viewer << SetSelectCallback3D( displayCoordsCallBack, &vectVoxels, 0, vectVoxels.size()-1 ); + mesh << meshName; + if (customAlphaMesh) + { + for (unsigned int j = 0; j < mesh.nbFaces(); j++) + { + auto c = mesh.getFaceColor(j); + mesh.setFaceColor(j, Color(c.red(), c.green(), c.blue(), customAlphaMesh)); + } + viewer << mesh; + } + if (interactiveDisplayVoxCoords) + { + viewer << SetSelectCallback3D(displayCoordsCallBack, &vectVoxels, 0, vectVoxels.size() - 1); + } + + viewer << Viewer3D<>::updateDisplay; + return application.exec(); } - - viewer << Viewer3D<>::updateDisplay; - return application.exec(); } } - diff --git a/visualisation/CMakeLists.txt b/visualisation/CMakeLists.txt index 58cbc111..57ffd96d 100644 --- a/visualisation/CMakeLists.txt +++ b/visualisation/CMakeLists.txt @@ -45,8 +45,23 @@ if ( WITH_VISU3D_QGLVIEWER ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Xml_EXECUTABLE_COMPILE_FLAGS}") DGtalTools_add_tool(sliceViewer ${ui_sliceViewer_FORMS_HEADERS}) endif (WITH_QT4) - endif (WITH_VISU3D_QGLVIEWER) +if ( NOT(DISABLE_POLYSCOPE) ) + SET(DGTALTOOLS_POLYSCOPE_SRC + volscope) + + FOREACH(tool_file ${DGTALTOOLS_POLYSCOPE_SRC}) + add_executable(${tool_file} ${tool_file}.cpp) + target_link_libraries (${tool_file} ${DGTAL_LIBRARIES} ${DGtalLibDependencies} ${DGtalToolsLibDependencies} polyscope) + install(TARGETS ${tool_file} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) + endforeach (tool_file) +endif () + + + diff --git a/visualisation/meshViewer.cpp b/visualisation/meshViewer.cpp index 84a87f5c..cf4a8803 100644 --- a/visualisation/meshViewer.cpp +++ b/visualisation/meshViewer.cpp @@ -37,28 +37,25 @@ #include "DGtal/helpers/StdDefs.h" #include "DGtal/io/readers/PointListReader.h" - #include "CLI11.hpp" - using namespace std; using namespace DGtal; - /** @page meshViewer meshViewer - + @brief Displays OFF mesh file by using QGLviewer. - + @b Usage: meshViewer [input] - + @b Allowed @b options @b are : - + @code - + Positionals: 1 TEXT:FILE ... REQUIRED inputFileNames.off files (.off), or OFS file (.ofs) - + Options: -h,--help Print this help message and exit -i,--input TEXT:FILE ... REQUIRED inputFileNames.off files (.off), or OFS file (.ofs) @@ -81,20 +78,20 @@ using namespace DGtal; -l,--fixLightToScene Fix light source to scence instead to camera -n,--invertNormal invert face normal vectors. -v,--drawVertex draw the vertex of the mesh - + @endcode - - + + @b Example: - + @code $ meshViewer bunny.off - + @endcode - + You should obtain such a result: @image html resMeshViewer.png "Resulting visualization." - + @see @ref meshViewer.cpp @@ -104,273 +101,331 @@ using namespace DGtal; * Custom Viewer3D to override KeyPressEvent method and handle new key display. * It also desactivate the double Rendering mode for more efficiency. **/ -class CustomViewer3D: public Viewer3D<> +class CustomViewer3D : public Viewer3D<> { protected: - virtual void init() { Viewer3D<>::init(); - Viewer3D<>::setKeyDescription ( Qt::Key_I, "Display mesh informations about #faces, #vertices" ); + Viewer3D<>::setKeyDescription(Qt::Key_I, "Display mesh informations about #faces, #vertices"); Viewer3D<>::setGLDoubleRenderingMode(false); - } - - virtual void keyPressEvent(QKeyEvent *e){ + + virtual void keyPressEvent(QKeyEvent *e) + { bool handled = false; - if( e->key() == Qt::Key_I) + if (e->key() == Qt::Key_I) { - handled=true; + handled = true; myIsDisplayingInfoMode = !myIsDisplayingInfoMode; stringstream ss; qglviewer::Vec camPos = camera()->position(); - DGtal::Z3i::RealPoint c (camPos[0], camPos[1], camPos[2]); - ss << myInfoDisplay << " distance to camera: " << (c-centerMesh).norm(); - Viewer3D<>::displayMessage(QString(myIsDisplayingInfoMode ? - ss.str().c_str() : " "), 1000000); - + DGtal::Z3i::RealPoint c(camPos[0], camPos[1], camPos[2]); + ss << myInfoDisplay << " distance to camera: " << (c - centerMesh).norm(); + Viewer3D<>::displayMessage(QString(myIsDisplayingInfoMode ? ss.str().c_str() : " "), 1000000); + Viewer3D<>::update(); } - if(!handled) + if (!handled) { Viewer3D<>::keyPressEvent(e); } }; - -public: + +public: void changeDefaultBGColor(const DGtal::Color &col) - { - myDefaultBackgroundColor = col; - Viewer3D<>::update(); - Viewer3D<>::draw(); - } + { + myDefaultBackgroundColor = col; + Viewer3D<>::update(); + Viewer3D<>::draw(); + } std::string myInfoDisplay = "No information loaded..."; bool myIsDisplayingInfoMode = false; DGtal::Z3i::RealPoint centerMesh; }; - -int main( int argc, char** argv ) +int main(int argc, char **argv) { - float sx {1.0}; - float sy {1.0}; - float sz {1.0}; - - unsigned int meshColorR {240}; - unsigned int meshColorG {240}; - unsigned int meshColorB {240}; - unsigned int meshColorA {255}; - - unsigned int meshColorRLine {0}; - unsigned int meshColorGLine {0}; - unsigned int meshColorBLine {0}; - unsigned int meshColorALine {255}; - - unsigned int sdpColorR {240}; - unsigned int sdpColorG {240}; - unsigned int sdpColorB {240}; - unsigned int sdpColorA {255}; - - float lineWidth {1.5}; - - std::vector customColorMesh; - std::vector customColorSDP; - std::vector customLineColor; - std::vector customBGColor; - std::vector vectFieldIndices = {0,1,2,3,4,5}; + float sx{1.0}; + float sy{1.0}; + float sz{1.0}; + + unsigned int meshColorR{240}; + unsigned int meshColorG{240}; + unsigned int meshColorB{240}; + unsigned int meshColorA{255}; + + unsigned int meshColorRLine{0}; + unsigned int meshColorGLine{0}; + unsigned int meshColorBLine{0}; + unsigned int meshColorALine{255}; + + unsigned int sdpColorR{240}; + unsigned int sdpColorG{240}; + unsigned int sdpColorB{240}; + unsigned int sdpColorA{255}; + + float lineWidth{1.5}; + + std::vector customColorMesh; + std::vector customColorSDP; + std::vector customLineColor; + std::vector customBGColor; + std::vector customAlphaMesh; + std::vector vectFieldIndices = {0, 1, 2, 3, 4, 5}; std::string displayVectorField; - + std::string snapshotFile; std::string filenameSDP; - double ballRadius {0.5}; - bool invertNormal {false}; - bool drawVertex {false}; - bool useLastCamSet {false}; - bool fixLightToScene {false}; - float ambiantLight {0.0}; - std::vector customAlphaMesh; - + double ballRadius{0.5}; + bool invertNormal{false}; + bool drawVertex{false}; + bool useLastCamSet{false}; + bool fixLightToScene{false}; + float ambiantLight{0.0}; + // parse command line using CLI ---------------------------------------------- CLI::App app; std::vector inputFileNames; - std::string outputFileName {"result.raw"}; + std::string outputFileName{"result.raw"}; app.description("Display OFF mesh file by using QGLviewer"); - app.add_option("-i,--input,1", inputFileNames, "inputFileNames.off files (.off), or OFS file (.ofs)" ) - ->check(CLI::ExistingFile) - ->required(); + app.add_option("-i,--input,1", inputFileNames, "inputFileNames.off files (.off), or OFS file (.ofs)") + ->check(CLI::ExistingFile) + ->required(); app.add_option("-x,--scaleX", sx, "set the scale value in the X direction (default 1.0)"); app.add_option("-y,--scaleY", sy, "set the scale value in the y direction (default 1.0)"); app.add_option("-z,--scaleZ", sz, "set the scale value in the z direction (default 1.0)"); app.add_option("--minLineWidth", lineWidth, "set the min line width of the mesh faces (default 1.5)", true); app.add_option("--customColorMesh", customColorMesh, "set the R, G, B, A components of the colors of the mesh faces and eventually the color R, G, B, A of the mesh edge lines (set by default to black)."); - app.add_option("--customAlphaMesh", customAlphaMesh, "set the alpha components of the colors of the mesh faces (can be applied for each mesh)."); - - app.add_option("--customColorSDP", customColorSDP, "set the R, G, B, A components of the colors of the sdp view") - ->expected(4); + app.add_option("--customAlphaMesh", customAlphaMesh, "set the alpha(A) components of the colors of the mesh faces (can be applied for each mesh)."); + app.add_option("--customColorSDP", customColorSDP, "set the R, G, B, A components of the colors of the sdp view") + ->expected(4); app.add_option("--displayVectorField,-f", displayVectorField, "display a vector field from a simple sdp file (two points per line)"); - app.add_option("--vectorFieldIndex", vectFieldIndices, "specify special indices for the two point coordinates (instead usinf the default indices: 0 1, 2, 3, 4, 5)" ) - ->expected(6); + app.add_option("--vectorFieldIndex", vectFieldIndices, "specify special indices for the two point coordinates (instead usinf the default indices: 0 1, 2, 3, 4, 5)") + ->expected(6); app.add_option("--customLineColor", customLineColor, "set the R, G, B components of the colors of the lines displayed from the --displayVectorField option (red by default).") - ->expected(4); + ->expected(4); app.add_option("--SDPradius", ballRadius, "change the ball radius to display a set of discrete points (used with displaySDP option)", true); - app.add_option("--displaySDP,-s", filenameSDP, "add the display of a set of discrete points as ball of radius 0.5."); - app.add_option("--addAmbientLight,-A", ambiantLight, "add an ambient light for better display (between 0 and 1)." ); + app.add_option("--displaySDP,-s", filenameSDP, "add the display of a set of discrete points as ball of radius 0.5."); + app.add_option("--addAmbientLight,-A", ambiantLight, "add an ambient light for better display (between 0 and 1)."); app.add_option("--customBGColor,-b", customBGColor, "set the R, G, B components of the colors of the background color.") - ->expected(3); + ->expected(3); app.add_option("--doSnapShotAndExit,-d", snapshotFile, "save display snapshot into file. Notes that the camera setting is set by default according the last saved configuration (use SHIFT+Key_M to save current camera setting in the Viewer3D). If the camera setting was not saved it will use the default camera setting."); app.add_flag("--useLastCameraSetting,-c", useLastCamSet, "use the last camera setting of the user (i.e if a .qglviewer.xml file is present in the current directory)"); app.add_flag("--fixLightToScene,-l", fixLightToScene, "Fix light source to scence instead to camera"); app.add_flag("--invertNormal,-n", invertNormal, "invert face normal vectors."); app.add_flag("--drawVertex,-v", drawVertex, "draw the vertex of the mesh"); - app.get_formatter()->column_width(40); CLI11_PARSE(app, argc, argv); // END parse command line using CLI ---------------------------------------------- - - - + DGtal::Color vFieldLineColor = DGtal::Color::Red; - if( customLineColor.size() == 4) + if (customLineColor.size() == 4) { vFieldLineColor.setRGBi(customLineColor[0], customLineColor[1], customLineColor[2], 255); } - - if( customColorMesh.size() != 0 ) + + if (customColorMesh.size() != 0) { - if(customColorMesh.size()<4 ) + if (customColorMesh.size() < 4) { - trace.error() << "colors specification should contain R,G,B and Alpha values"<< std::endl; + trace.error() << "colors specification should contain R,G,B and Alpha values" << std::endl; } - } - - if(customColorSDP.size() == 4) + + if (customColorSDP.size() == 4) { sdpColorR = customColorSDP[0]; sdpColorG = customColorSDP[1]; sdpColorB = customColorSDP[2]; sdpColorA = customColorSDP[3]; } - - QApplication application(argc,argv); + + QApplication application(argc, argv); CustomViewer3D viewer; - if(snapshotFile != "") + if (snapshotFile != "") { viewer.setSnapshotFileName(QString(snapshotFile.c_str())); } - + std::stringstream title; - title << "Simple Mesh Viewer: " << inputFileNames[0]; + title << "Simple Mesh Viewer: " << inputFileNames[0]; viewer.setWindowTitle(title.str().c_str()); viewer.show(); viewer.myGLLineMinWidth = lineWidth; viewer.setGLScale(sx, sy, sz); - if (customBGColor.size() == 3){ - viewer.changeDefaultBGColor(DGtal::Color(customBGColor[0], - customBGColor[1], - customBGColor[2], 255)); + if (customBGColor.size() == 3) + { + viewer.changeDefaultBGColor(DGtal::Color(customBGColor[0], + customBGColor[1], + customBGColor[2], 255)); } - if(ambiantLight != 0.0) + if (ambiantLight != 0.0) { - GLfloat lightAmbientCoeffs [4] = {ambiantLight,ambiantLight, ambiantLight, 1.0f}; + GLfloat lightAmbientCoeffs[4] = {ambiantLight, ambiantLight, ambiantLight, 1.0f}; viewer.setGLLightAmbientCoefficients(lightAmbientCoeffs); } - + trace.info() << "Importing mesh... "; - - std::vector > vectMesh; - for(unsigned int i = 0; i< inputFileNames.size(); i++) + + std::vector> vectMesh; + for (unsigned int i = 0; i < inputFileNames.size(); i++) { Mesh aMesh(customColorMesh.size() != 4 && customColorMesh.size() != 8); aMesh << inputFileNames[i]; - // for obj mesh by default the mesh color face are not necessary uniform. - if (aMesh.isStoringFaceColors() && customColorMesh.size() >= 4 ){ - if ( i*8 < customColorMesh.size() ) {meshColorR = customColorMesh[i*8];} - if ( i*8+1< customColorMesh.size() ) {meshColorG = customColorMesh[i*8+1];} - if ( i*8+2 < customColorMesh.size() ) {meshColorB = customColorMesh[i*8+2];} - if ( i*8+3 < customColorMesh.size() ) {meshColorA = customColorMesh[i*8+3];} - for (unsigned int j = 0; j < aMesh.nbFaces(); j++){ - aMesh.setFaceColor(j, Color(meshColorR, meshColorG, meshColorB, meshColorA)); - } - }else if (customAlphaMesh.size() > 0 ) { - for (unsigned int j = 0; j < aMesh.nbFaces(); j++){ - auto c = aMesh.getFaceColor(j); - aMesh.setFaceColor(j, Color(c.red(), c.green(), c.blue(), customAlphaMesh.at(i= 4) + { + if (i * 8 < customColorMesh.size()) + { + meshColorR = customColorMesh[i * 8]; } - + if (i * 8 + 1 < customColorMesh.size()) + { + meshColorG = customColorMesh[i * 8 + 1]; + } + if (i * 8 + 2 < customColorMesh.size()) + { + meshColorB = customColorMesh[i * 8 + 2]; + } + if (i * 8 + 3 < customColorMesh.size()) + { + meshColorA = customColorMesh[i * 8 + 3]; + } + for (unsigned int j = 0; j < aMesh.nbFaces(); j++) + { + aMesh.setFaceColor(j, Color(meshColorR, meshColorG, meshColorB, meshColorA)); + } + } + else if (customAlphaMesh.size() > 0) + { + for (unsigned int j = 0; j < aMesh.nbFaces(); j++) + { + auto c = aMesh.getFaceColor(j); + aMesh.setFaceColor(j, Color(c.red(), c.green(), c.blue(), customAlphaMesh.at(i < customAlphaMesh.size() ? i : 0))); + } + } + vectMesh.push_back(aMesh); } DGtal::Z3i::RealPoint centerMeshes; - unsigned int tot=0; - for(const auto & m: vectMesh) + unsigned int tot = 0; + for (const auto &m : vectMesh) { - for( auto p = m.vertexBegin(); p!=m.vertexEnd(); ++p) + for (auto p = m.vertexBegin(); p != m.vertexEnd(); ++p) centerMeshes += *p; - tot+=m.nbVertex(); + tot += m.nbVertex(); } centerMeshes /= tot; viewer.centerMesh = centerMeshes; - bool import = vectMesh.size()==inputFileNames.size(); - if(!import) + bool import = vectMesh.size() == inputFileNames.size(); + if (!import) { trace.info() << "File import failed. " << std::endl; return 0; } - - trace.info() << "[done]. "<< std::endl; - if(filenameSDP != "") + + trace.info() << "[done]. " << std::endl; + if (filenameSDP != "") { - vector vectPoints; - vectPoints = PointListReader::getPointsFromFile(filenameSDP); - viewer << CustomColors3D(Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA), - Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA)); - for(unsigned int i=0;i< vectPoints.size(); i++){ - viewer.addBall(vectPoints.at(i), ballRadius); + if (customAlphaMesh.size() > 0) + { + trace.info() << "New meshViewer" << std::endl; + auto vOrigins = PointListReader>::getPolygonsFromFile(filenameSDP); + viewer << CustomColors3D(Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA), + Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA)); + for (auto l : vOrigins) + { + DGtal::Z3i::Point pt(l[0][0], l[1][0], l[2][0]); + DGtal::Color cl(l[3][0], l[4][0], l[5][0], sdpColorA); + viewer.setFillColor(cl); + viewer.addBall(pt, ballRadius); + } + } + else + { + vector vectPoints; + vectPoints = PointListReader::getPointsFromFile(filenameSDP); + viewer << CustomColors3D(Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA), + Color(sdpColorR, sdpColorG, sdpColorB, sdpColorA)); + for (unsigned int i = 0; i < vectPoints.size(); i++) + { + viewer.addBall(vectPoints.at(i), ballRadius); + } } } - if(invertNormal) + if (invertNormal) { - for(unsigned int i=0; i::VertexStorage::const_iterator it = vectMesh[i].vertexBegin(); - it!=vectMesh[i].vertexEnd(); ++it){ + + if (drawVertex) + { + for (unsigned int i = 0; i < vectMesh.size(); i++) + { + for (Mesh::VertexStorage::const_iterator it = vectMesh[i].vertexBegin(); + it != vectMesh[i].vertexEnd(); ++it) + { DGtal::Z3i::Point pt; - pt[0]=(*it)[0]; pt[1]=(*it)[1]; pt[2]=(*it)[2]; + pt[0] = (*it)[0]; + pt[1] = (*it)[1]; + pt[2] = (*it)[2]; viewer << pt; } } } - + if (displayVectorField != "") { - std::vector vectFieldIndices1 = {vectFieldIndices[0],vectFieldIndices[1], vectFieldIndices[2]}; - std::vector vectFieldIndices2 = {vectFieldIndices[3],vectFieldIndices[4], vectFieldIndices[5]}; + std::vector vectFieldIndices1 = {vectFieldIndices[0], vectFieldIndices[1], vectFieldIndices[2]}; + std::vector vectFieldIndices2 = {vectFieldIndices[3], vectFieldIndices[4], vectFieldIndices[5]}; std::vector vectPt1 = PointListReader::getPointsFromFile(displayVectorField, vectFieldIndices1); std::vector vectPt2 = PointListReader::getPointsFromFile(displayVectorField, vectFieldIndices2); viewer.createNewLineList(); @@ -382,44 +437,44 @@ int main( int argc, char** argv ) } unsigned int nbVertex = 0; unsigned int nbFaces = 0; - for(auto const &m: vectMesh) + for (auto const &m : vectMesh) { nbVertex += m.nbVertex(); - nbFaces +=m.nbFaces(); + nbFaces += m.nbFaces(); } stringstream ss; - ss << "# faces: " << std::fixed << nbFaces << " #vertex: " << nbVertex ; + ss << "# faces: " << std::fixed << nbFaces << " #vertex: " << nbVertex; viewer.myInfoDisplay = ss.str(); - viewer << CustomViewer3D::updateDisplay; + viewer << CustomViewer3D::updateDisplay; if (fixLightToScene) { - viewer.setLightModeFixToCamera(false, false); + viewer.setLightModeFixToCamera(false, false); } if (useLastCamSet) { - viewer.restoreStateFromFile(); + viewer.restoreStateFromFile(); } else { - // useful in non interactive case in order to retain the default camera settings (that are not saved in case of process kill). - viewer.saveStateToFile(); + // useful in non interactive case in order to retain the default camera settings (that are not saved in case of process kill). + viewer.saveStateToFile(); } // First display transparency improve viewer.sortTriangleFromCamera(); viewer.sortQuadFromCamera(); viewer.sortSurfelFromCamera(); viewer.sortPolygonFromCamera(); - viewer << CustomViewer3D::updateDisplay; - - if(snapshotFile != "" ) + viewer << CustomViewer3D::updateDisplay; + + if (snapshotFile != "") { // Recover mesh position viewer.restoreStateFromFile(); viewer.saveSnapshot(QString(snapshotFile.c_str()), true); return 0; } - trace.info() << "[display ready]"<< std::endl; + trace.info() << "[display ready]" << std::endl; return application.exec(); } diff --git a/visualisation/volscope.cpp b/visualisation/volscope.cpp new file mode 100644 index 00000000..f98e7684 --- /dev/null +++ b/visualisation/volscope.cpp @@ -0,0 +1,198 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ +/** + * @file volscope.cpp + * @ingroup visualization + * @author David Coeurjolly (\c david.coeurjolly@cnrs.fr ) + * Laboratoire d'InfoRmatique en Image et Systèmes d'information - LIRIS (CNRS, UMR 5205), CNRS, + * France + * + * @date 2023/12/22 + * + * Vol visualization using polyscope. + * + * This file is part of the DGtal library. + */ + +#include +#include + +#include +#include +#include + +#include +#include "polyscope/surface_mesh.h" +#include "polyscope/volume_mesh.h" +#include "polyscope/point_cloud.h" + +#include "CLI11.hpp" + +// Using standard 3D digital space. +using namespace DGtal; +using namespace Z3i; +typedef Shortcuts SH3; + + +/** + @page volscope volscope + + @brief Volumetric file visualization using polyscope + + @b Usage: volscope [options] --input \ + + + @b Allowed @b options @b are : + @code + Usage: ./volscope [OPTIONS] 1 + + Positionals: + 1 TEXT:FILE REQUIRED Input vol file. + + Options: + -h,--help Print this help message and exit + -i,--input TEXT:FILE REQUIRED Input vol file. + --volumetricMode Activate the volumetric mode instead of the isosurface visualization. + --point-cloud-only In the volumetric mode, visualize the vol file as a point cloud instead of an hex mesh (default: false) +-m, --min INT For isosurface visualization and voxel filtering, specifies the threshold min (excluded) (default: 0). + -M, --max INT For isosurface visualization and voxel filtering, specifies the threshold max (included) (default: 255). + @endcode + + + @image html volscope-surface.png "Default visualization mode." width=50% + + @image html volscope-volumetric.png "Volumetric visualization using polyscope slice planes." width=50% + + + + +**/ + +int main(int argc, char**argv) +{ + // parse command line using CLI ---------------------------------------------- + CLI::App app; + app.description("Vol file vizualization using polyscope"); + + std::string inputFileName; + app.add_option("-i,--input,1", inputFileName, "Input vol file.")->required()->check(CLI::ExistingFile); + + bool volumetricMode = false; + app.add_flag("--volumetricMode",volumetricMode, "Activate the volumetric mode instead of the isosurface visualization."); + + bool pclOnly = false; + app.add_flag("--point-cloud-only",pclOnly, "In the volumetric mode, visualize the vol file as a point cloud instead of an hex mesh (default: false)"); + + int thresholdMin=0; + app.add_option("--min,--thresholdMin,-m", thresholdMin, "For isosurface visualization and voxel filtering, specifies the threshold min (excluded) (default: 0)."); + int thresholdMax=255; + app.add_option("--max, --thresholdMax,-M", thresholdMax, "For isosurface visualization and voxel filtering, specifies the threshold max (included) (default: 255)."); + + + app.get_formatter()->column_width(40); + CLI11_PARSE(app, argc, argv); + + polyscope::options::programName = "volscope " + inputFileName + " - (DGtalTools)"; + polyscope::init(); + + if(!volumetricMode) + { + trace.beginBlock("Loading vol"); + auto params = SH3::defaultParameters(); + params("thresholdMin",thresholdMin)("thresholdMax",thresholdMax); + auto volimage = SH3::makeBinaryImage(inputFileName, params ); + auto K = SH3::getKSpace( volimage ); + auto surface = SH3::makeLightDigitalSurface( volimage, K, params ); + auto primalSurface = SH3::makePrimalSurfaceMesh(surface); + trace.endBlock(); + + trace.beginBlock("Creating polyscope surface"); + std::vector> faces; + for(auto face= 0 ; face < primalSurface->nbFaces(); ++face) + faces.push_back(primalSurface->incidentVertices( face )); + polyscope::registerSurfaceMesh("Vol file", primalSurface->positions(), faces); + trace.endBlock(); + } + else + { + std::vector vertexPos; + std::vector pclPos; + std::vector pclData; + std::vector> hexIndices; + std::vector hexData; + + trace.beginBlock("Loading vol"); + auto volimage = SH3::makeGrayScaleImage(inputFileName); + trace.endBlock(); + trace.beginBlock("Creating polyscope HexMesh/Point Cloud"); + auto dom = volimage->domain(); + auto W = dom.upperBound() - dom.lowerBound() + Point::diagonal(1); + auto WW = W + Point::diagonal(1); //for dual grid + trace.info()< hex; + for(auto k=0; k <= W[2]; ++k) + for(auto j=0; j <= W[1]; ++j) + for(auto i=0; i <= W[0]; ++i) + { + p=Point(i,j,k); + if ((ithresholdMin) && (val <=thresholdMax)) + { + pclPos.push_back(p); + pclData.push_back(val); + } + } + else + { + vertexPos.push_back(p+dom.lowerBound() -RealPoint::diagonal(0.5) ); + hex = { cpt, cpt +1 , cpt + 1 + WW[0] , cpt +WW[0] , cpt + WW[0]*WW[1], cpt +1 + WW[0]*WW[1], cpt + 1 + WW[0]*WW[1]+WW[0] , cpt + WW[0]*WW[1]+WW[0]}; + + if (((i+1)< WW[0]) && ((j+1)< WW[1]) && ((k+1)< WW[2])&& (val>thresholdMin) && (val <=thresholdMax)) + { + hexData.push_back(val); + hexIndices.push_back(hex); + } + ++cpt; + } + } + + if (pclOnly) + { + auto ps = polyscope::registerPointCloud("Vol file", pclPos); + ps->addScalarQuantity("values", pclData); + trace.info()<<"Nb vertices ="<addCellScalarQuantity("values", hexData); + trace.info()<<"Nb points ="< MyImageC; + // parse command line using CLI ---------------------------------------------- CLI::App app; std::string inputFileName; std::string outputFileName {"result.vol"}; + MyImageC::Value fillValue = 128; app.description("Fill the interior of a voxel set by filling the exterior using the 6-adjacency.\nThe exterior is the set of voxels with value zero and the interior voxels have value 128\n Basic usage:\n\tvolFillInterior "); @@ -98,14 +105,15 @@ int main(int argc, char**argv) ->required() ->check(CLI::ExistingFile); app.add_option("-o,--output,2",outputFileName, "Output filename.", true); - + app.add_option("-v,--fillValue,3", fillValue, "Set the filling value other than the default value of 128.", false); + app.get_formatter()->column_width(40); CLI11_PARSE(app, argc, argv); // END parse command line using CLI ---------------------------------------------- trace.beginBlock("Loading"); - typedef ImageContainerBySTLVector MyImageC; + MyImageC image = VolReader< MyImageC >::importVol ( inputFileName ); trace.info() << image << std::endl; trace.endBlock(); @@ -142,7 +150,7 @@ int main(int argc, char**argv) trace.beginBlock("Complement"); for(auto &p : image.domain()) if ((image(p) == 0) && (!imageFlag(p))) - image.setValue(p,128); + image.setValue(p, fillValue); trace.endBlock(); trace.beginBlock("Saving");