diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9759f76 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 2.8.12) +#To avoid OpenMP warning: https://gitlab.kitware.com/cmake/cmake/issues/17292 +if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +project(proxTV + DESCRIPTION "Toolbox for fast Total Variation proximity operators" + LANGUAGES C CXX + ) + +# Set VERSION +# Since cmake v3.0 we can just give an option to project(proxTV VERSION 3.2.1 LANGUAGES C CXX) +set(proxTV_VERSION_MAJOR 3) +set(proxTV_VERSION_MINOR 2) +set(proxTV_VERSION_PATCH 1) +# set(proxTV_VERSION_TWEAK 0) +set(proxTV_VERSION "${proxTV_VERSION_MAJOR}.${proxTV_VERSION_MINOR}.${proxTV_VERSION_PATCH}") +message(STATUS "proxTV version: ${proxTV_VERSION}") + +# External Dependencies: {{{ +# Use FindLAPACKE from local cmake folder +# FindLAPACK, FindOpenMP are already included in cmake installation. +# Threads is for pthreads flags. +# Append local module directory to the find_package search: +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +find_package(LAPACKE REQUIRED) +message(STATUS "Lapacke found?: ${LAPACKE_FOUND}") +message(STATUS "Lapacke libraries: ${LAPACKE_LIBRARIES}") + +find_package(LAPACK REQUIRED) +message(STATUS "Lapack found?: ${LAPACKE_FOUND}") +message(STATUS "Lapack libraries: ${LAPACK_LIBRARIES}") + +find_package(Threads) + +find_package(OpenMP) +message(STATUS "OpenMP found?: ${OpenMP_FOUND}") + +add_subdirectory(src) + +# Test +option(ENABLE_TESTING "Compile tests" OFF) +if(${ENABLE_TESTING}) + enable_testing() + add_subdirectory(test) +endif() diff --git a/README.md b/README.md index 3d1b90d..97d3ef5 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,33 @@ More technically, the library provides efficient solvers for the following Total | Anisotropic Total Variation on a 3-dimensional signal (video denoising) | ![alt tag](docs/img/TV3D.png) | | Generalized N-dimensional Anisotropic Total Variation (tensor denoising) | ![alt tag](docs/img/TVND.png), with X(di) every possible 1-dimensional slice of X following dimension di.| +## C interface + +You can use the **c** library using **cmake**. If `libproxTV` is not provided by your package-manager, install it from source: + + mkdir proxTV-dev ; cd proxTV-dev + git clone https://github.com/albarji/proxTV proxTV + mkdir build ; cd build + cmake ../proxTV + make -j12 + make install + +The required dependencies are `lapack` and `lapacke`, the c-interface for `lapack`, and optionally, but recommended: `OpenMP` with `pthreads` for multi-threading support. + +You can provide extra options to `cmake` via the command line or a gui (i.e `ccmake`). + + cmake ../proxTV -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_INSTALL_PREFIX=/opt/ -DENABLE_TESTING:BOOL=ON + +To use proxTV in your `cmake` project just write in your `CMakeLists.txt`: + + find_package(proxTV) + add_executable(foo main.cpp) + target_link_libraries(foo PUBLIC proxTV::proxTV) + +That will propagate all the dependencies of proxTV to your target. If you haven't installed proxTV in a system folder, you have to point to the installation directory when configuring your project with `cmake`. + + cmake /path/my_project_source_folder -DproxTV_DIR:PATH="/proxTV_install_folder/lib/cmake/proxTV" + ## Python interface ### Quickstart diff --git a/cmake/FindLAPACKE.cmake b/cmake/FindLAPACKE.cmake new file mode 100644 index 0000000..20546ad --- /dev/null +++ b/cmake/FindLAPACKE.cmake @@ -0,0 +1,190 @@ +#.rst: +# FindLAPACKE +# ------------- +# +# Find the LAPACKE library +# +# Using LAPACKE: +# +# :: +# +# find_package(LAPACKE REQUIRED) +# include_directories(${LAPACKE_INCLUDE_DIRS}) +# add_executable(foo foo.cc) +# target_link_libraries(foo ${LAPACKE_LIBRARIES}) +# +# This module sets the following variables: +# +# :: +# +# LAPACKE_FOUND - set to true if the library is found +# LAPACKE_INCLUDE_DIRS - list of required include directories +# LAPACKE_LIBRARIES - list of libraries to be linked +# LAPACKE_VERSION_MAJOR - major version number +# LAPACKE_VERSION_MINOR - minor version number +# LAPACKE_VERSION_PATCH - patch version number +# LAPACKE_VERSION_STRING - version number as a string (ex: "0.2.18") + +#============================================================================= +# Copyright 2016 Hans J. Johnson +# +# Distributed under the OSI-approved BSD License (the "License") +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#============================================================================= +# +set(LAPACKE_SEARCH_PATHS + ${LAPACKE_DIR} + $ENV{LAPACKE_DIR} + $ENV{CMAKE_PREFIX_PATH} + ${CMAKE_PREFIX_PATH} + /usr + /usr/local + /usr/local/opt/lapack ## Mac Homebrew install path + /opt/LAPACKE +) +message(STATUS "LAPACKE_SEARCH_PATHS: ${LAPACKE_SEARCH_PATHS}") + +set(CMAKE_PREFIX_PATH ${LAPACKE_SEARCH_PATHS}) +list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH) + +## First try to find LAPACKE with NO_MODULE, +## As of 20160706 version 0.2.18 there is limited cmake support for LAPACKE +## that is not as complete as this version, if found, use it +## to identify the LAPACKE_VERSION_STRING and improve searching. +find_package(LAPACKE NO_MODULE QUIET) +if(LAPACKE_FOUND) + if(EXISTS ${LAPACKE_DIR}/lapacke-config-version.cmake) + include(${LAPACKE_DIR}/lapacke-config-version.cmake) + set(LAPACKE_VERSION_STRING ${PACKAGE_VERSION}) + unset(PACKAGE_VERSION) # Use cmake conventional naming + endif() + find_package(LAPACK NO_MODULE QUIET) #Require matching versions here! + find_package(BLAS NO_MODULE QUIET) #Require matching versions here! +endif() + +################################################################################################## +### First search for headers +find_path(LAPACKE_CBLAS_INCLUDE_DIR + NAMES cblas.h + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES include include/lapack) +find_path(LAPACKE_LAPACKE_INCLUDE_DIR + NAMES lapacke.h + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES include) + +################################################################################################## +### Second, search for libraries +set(PATH_SUFFIXES_LIST + lib64 + lib +) +find_library(LAPACKE_LIB + NAMES lapacke + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES ${PATH_SUFFIXES_LIST}) +find_library(CBLAS_LIB + NAMES cblas + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES ${PATH_SUFFIXES_LIST}) +find_library(LAPACK_LIB + NAMES lapack + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES ${PATH_SUFFIXES_LIST}) +find_library(BLAS_LIB + NAMES blas + PATHS ${LAPACKE_SEARCH_PATHS} + PATH_SUFFIXES ${PATH_SUFFIXES_LIST}) + +## TODO: Get version components +# ------------------------------------------------------------------------ +# Extract version information +# ------------------------------------------------------------------------ + +# WARNING: We may not be able to determine the version of some LAPACKE +set(LAPACKE_VERSION_MAJOR 0) +set(LAPACKE_VERSION_MINOR 0) +set(LAPACKE_VERSION_PATCH 0) +if(LAPACKE_VERSION_STRING) + string(REGEX REPLACE "([0-9]+).([0-9]+).([0-9]+)" "\\1" LAPACKE_VERSION_MAJOR "${LAPACKE_VERSION_STRING}") + string(REGEX REPLACE "([0-9]+).([0-9]+).([0-9]+)" "\\2" LAPACKE_VERSION_MINOR "${LAPACKE_VERSION_STRING}") + string(REGEX REPLACE "([0-9]+).([0-9]+).([0-9]+)" "\\3" LAPACKE_VERSION_PATCH "${LAPACKE_VERSION_STRING}") +endif() + +#====================== +# Checks 'REQUIRED', 'QUIET' and versions. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LAPACKE FOUND_VAR LAPACKE_FOUND + REQUIRED_VARS LAPACKE_CBLAS_INCLUDE_DIR + LAPACKE_LAPACKE_INCLUDE_DIR + LAPACKE_LIB + LAPACK_LIB + CBLAS_LIB + BLAS_LIB + VERSION_VAR LAPACKE_VERSION_STRING +) + +if (LAPACKE_FOUND) + set(LAPACKE_INCLUDE_DIRS ${LAPACKE_CBLAS_INCLUDE_DIR} ${LAPACKE_CBLAS_INCLUDE_DIR}) + list(REMOVE_DUPLICATES LAPACKE_INCLUDE_DIRS) + if("${CMAKE_C_COMPILER_ID}" MATCHES ".*Clang.*" OR + "${CMAKE_C_COMPILER_ID}" MATCHES ".*GNU.*" OR + "${CMAKE_C_COMPILER_ID}" MATCHES ".*Intel.*" + ) #NOT MSVC + set(MATH_LIB m) + endif() + list(APPEND LAPACKE_LIBRARIES ${LAPACKE_LIB} ${LAPACK_LIB} ${BLAS_LIB} ${CBLAS_LIB}) + # Check for a common combination, and find required gfortran support libraries + + if(1) + if("${CMAKE_C_COMPILER_ID}" MATCHES ".*Clang.*" AND "${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + message(STATUS "\n\n WARNING: ${CMAKE_C_COMPILER} identified as ${CMAKE_C_COMPILER_ID}\n" + "AND: ${CMAKE_Fortran_COMPILER} identified as ${CMAKE_Fortran_COMPILER_ID}\n" + "\n" + "may be require special configurations. The most common is the need to" + "explicitly link C programs against the gfortran support library.") + + endif() + else() + ## This code automated code is hard to determine if it is robust in many different environments. + # Check for a common combination, and find required gfortran support libraries + if("${CMAKE_C_COMPILER_ID}" MATCHES ".*Clang.*" AND "${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + include(FortranCInterface) + FortranCInterface_VERIFY() + if(NOT FortranCInterface_VERIFIED_C) + message(FATAL_ERROR "C and fortran compilers are not compatible:\n${CMAKE_Fortran_COMPILER}:${CMAKE_C_COMPILER}") + endif() + + execute_process(COMMAND ${CMAKE_Fortran_COMPILER} -print-file-name=libgfortran.a OUTPUT_VARIABLE FORTRANSUPPORTLIB ERROR_QUIET) + string(STRIP ${FORTRANSUPPORTLIB} FORTRANSUPPORTLIB) + if(EXISTS "${FORTRANSUPPORTLIB}") + list(APPEND LAPACKE_LIBRARIES ${FORTRANSUPPORTLIB}) + message(STATUS "Appending fortran support lib: ${FORTRANSUPPORTLIB}") + else() + message(FATAL_ERROR "COULD NOT FIND libgfortran.a support library:${FORTRANSUPPORTLIB}:") + endif() + endif() + endif() + list(APPEND LAPACKE_LIBRARIES ${MATH_LIB}) +endif() + +mark_as_advanced( + LAPACKE_FOUND + LAPACKE_INCLUDE_DIRS + LAPACKE_LIBRARIES + LAPACKE_VERSION_MAJOR + LAPACKE_VERSION_MINOR + LAPACKE_VERSION_PATCH + LAPACKE_VERSION_STRING +) + +## For debugging +message(STATUS "LAPACKE_FOUND :${LAPACKE_FOUND}: - set to true if the library is found") +message(STATUS "LAPACKE_INCLUDE_DIRS :${LAPACKE_INCLUDE_DIRS}: - list of required include directories") +message(STATUS "LAPACKE_LIBRARIES :${LAPACKE_LIBRARIES}: - list of libraries to be linked") +message(STATUS "LAPACKE_VERSION_MAJOR :${LAPACKE_VERSION_MAJOR}: - major version number") +message(STATUS "LAPACKE_VERSION_MINOR :${LAPACKE_VERSION_MINOR}: - minor version number") +message(STATUS "LAPACKE_VERSION_PATCH :${LAPACKE_VERSION_PATCH}: - patch version number") +message(STATUS "LAPACKE_VERSION_STRING :${LAPACKE_VERSION_STRING}: - version number as a string") diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 0000000..be07c7c --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,7 @@ +Modern lapacke provides a lapacke-config.cmake (minimum version unconfirmed, but 3.7.1 does) +so FindLAPACKE.cmake wouldn't be neccesary, (see #38) +however this isn't reliable for older/other lapacke versions, so we provide a FindModule from: https://github.com/mrirecon/bart/blob/master/cmake/FindLAPACKE.cmake. + +Download with `curl -O https://raw.githubusercontent.com/mrirecon/bart/master/cmake/FindLAPACKE.cmake` + +This FindLapacke handles properly the case a lapacke-config.cmake exists in the system. diff --git a/cmake/proxTVConfig.cmake b/cmake/proxTVConfig.cmake new file mode 100644 index 0000000..a1b4114 --- /dev/null +++ b/cmake/proxTVConfig.cmake @@ -0,0 +1,6 @@ +include(CMakeFindDependencyMacro) +find_dependency(LAPACKE) +find_dependency(LAPACK) +find_dependency(OpenMP) +find_dependency(Threads) +include("${CMAKE_CURRENT_LIST_DIR}/proxTVTargets.cmake") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2f01eaa..8d3d869 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,7 @@ -cmake_minimum_required(VERSION 2.8.12) -#To avoid OpenMP warning: https://gitlab.kitware.com/cmake/cmake/issues/17292 -if(POLICY CMP0054) - cmake_policy(SET CMP0054 NEW) -endif() +set(LIB_INSTALL_DIR lib CACHE STRING "Install folder for lib (relative") +set(INCLUDE_INSTALL_DIR include CACHE STRING "Install folder for headers (relative") +set(EXECUTABLE_INSTALL_DIR bin CACHE STRING "Install folder for executables (relative") -project(proxTV) set(headers condat_fast_tv.h general.h @@ -33,71 +30,62 @@ set(sources utils.cpp ) -# External Dependencies: - -find_package (Threads) -find_package(LAPACKE REQUIRED) -message(STATUS "Lapacke found?: ${LAPACKE_FOUND}") -message(STATUS "Lapacke libraries: ${LAPACKE_LIBRARIES}") -find_package(LAPACK REQUIRED) -message(STATUS "Lapack found?: ${LAPACKE_FOUND}") -message(STATUS "Lapack libraries: ${LAPACK_LIBRARIES}") - -find_package(OpenMP) -message(STATUS "OpenMP found?: ${OpenMP_FOUND}") -# No way to do it via targets, do it old way. -if(${OpenMP_FOUND}) - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") -endif() - add_library(proxTV ${sources}) -target_compile_definitions(proxTV PUBLIC - NOMATLAB - ) +target_compile_definitions(proxTV PUBLIC NOMATLAB) # Create directory with headers at build and install time. file(COPY ${headers} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include") install(FILES ${headers} - DESTINATION include) + DESTINATION ${INCLUDE_INSTALL_DIR}) target_include_directories(proxTV PUBLIC $ - $ + $ ${LAPACKE_INCLUDE_DIRS} ) +# Lapacke, Lapack, pthreads target_link_libraries(proxTV PUBLIC ${LAPACKE_LIBRARIES} ${LAPACK_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) +# OpenMP +target_compile_options(proxTV PUBLIC ${OpenMP_CXX_FLAGS}) +target_link_libraries(proxTV PUBLIC ${OpenMP_CXX_LIBRARIES}) + install(TARGETS proxTV EXPORT proxTVTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${EXECUTABLE_INSTALL_DIR} + INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR} ) + +# This saves as to develop a hacky FindproxTV for other to use the library. +# A regular find_package(proxTV) will look for these targets. +# It needs an extra proxTVConfig.cmake to handle dependencies (provided in cmake directory) install(EXPORT proxTVTargets FILE proxTVTargets.cmake NAMESPACE proxTV:: - DESTINATION lib/cmake/proxTV + DESTINATION ${LIB_INSTALL_DIR}/cmake/proxTV ) -# Test -option(ENABLE_TESTING "Compile tests" OFF) -if(${ENABLE_TESTING}) - enable_testing() - add_executable(test_tv1_2d "./test/test_tv1_2d.cpp") - target_include_directories(test_tv1_2d PUBLIC - $) - target_compile_definitions(test_tv1_2d PUBLIC NOMATLAB) - target_link_libraries(test_tv1_2d - PUBLIC - proxTV) - add_test(NAME tv1_2d COMMAND test_tv1_2d) -endif() +# Generate and install proxTVConfigVersion.cmake +include(CMakePackageConfigHelpers) +write_basic_package_version_file("proxTVConfigVersion.cmake" + VERSION ${proxTV_VERSION} + COMPATIBILITY SameMajorVersion + ) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/proxTVConfigVersion.cmake + DESTINATION ${LIB_INSTALL_DIR}/cmake/proxTV + ) +# Install proxTVConfig.cmake +install(FILES + ${CMAKE_SOURCE_DIR}/cmake/proxTVConfig.cmake + DESTINATION ${LIB_INSTALL_DIR}/cmake/proxTV + ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..1541962 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,21 @@ +set(test_names + test_tv1_2d + #other_test + ) + +add_executable(test_tv1_2d "test_tv1_2d.cpp") +add_test(NAME tv1_2d COMMAND test_tv1_2d) + +# To add more tests: append a name to test_names, and add_executable/add_test with that name +# Tests can be run with `ctest` in the build directory, +# it accepts regex, and different verbosity: ctest -R tv1 -VV +# add_executable(other_test "a_test_source_file.cpp") +# add_test(NAME a_name_that_makes_sense COMMAND other_test) + +# Link proxTV and include_directory for each executable. +foreach(test_executable ${test_names}) + target_include_directories(${test_executable} PUBLIC + $) + target_compile_definitions(${test_executable} PUBLIC NOMATLAB) + target_link_libraries(${test_executable} PUBLIC proxTV) +endforeach() diff --git a/test/project_using_proxTV/CMakeLists.txt b/test/project_using_proxTV/CMakeLists.txt new file mode 100644 index 0000000..555ff14 --- /dev/null +++ b/test/project_using_proxTV/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) +project(dummy) +find_package(proxTV) +add_executable(dummy dummy.cpp) +# All the requirements of proxTV: compiler definitions, include_directories, linker flags (INTERFACE) are propagated to the target. +target_link_libraries(dummy PUBLIC proxTV::proxTV ) diff --git a/test/project_using_proxTV/README.md b/test/project_using_proxTV/README.md new file mode 100644 index 0000000..3cdac16 --- /dev/null +++ b/test/project_using_proxTV/README.md @@ -0,0 +1,12 @@ +This a minimal project using proxTV as a third-party. +To compile it, it requires that proxTV is installed somewhere in your system. +The installation folder can be modified when configuring proxTV with the option + + -DCMAKE_INSTALL_PREFIX:PATH="/proxTV_install_folder" + +The default is usually the system folder `/usr`. Please read https://github.com/albarji/proxTV/README.md#c-interface for more info. + +Once installed in your system, if it is not in your system path, provide the folder where the `proxTVConfig.cmake` file is located with the option `-proxTV_DIR`: + + mkdir ~/dummy_project ; cd ~/dummy_project + cmake /path/proxTV/test/project_using_proxTV/ -DproxTV_DIR:PATH="/proxTV_install_folder/lib/cmake/proxTV" diff --git a/src/test/test_tv1_2d.cpp b/test/project_using_proxTV/dummy.cpp similarity index 100% rename from src/test/test_tv1_2d.cpp rename to test/project_using_proxTV/dummy.cpp diff --git a/test/test_tv1_2d.cpp b/test/test_tv1_2d.cpp new file mode 100644 index 0000000..876a299 --- /dev/null +++ b/test/test_tv1_2d.cpp @@ -0,0 +1,26 @@ +#include "TVopt.h" + + +int main() +{ +// int DR2_TV(size_t M, size_t N, double*unary, double W1, double W2, double norm1, double norm2, double*s, int nThreads, int maxit, double* info); + + size_t M = 1; + size_t N = 1; + double* unary = new double; + double W1 = 1.0; + double W2 = 1.0; + double norm1 = 1.0; + double norm2 = 1.0; + double* s = new double; + int nThreads = 1; + int maxit = 1; + double* info = new double; + int r = DR2_TV(M, N, unary, W1, W2, norm1, norm2, s, nThreads, maxit, info); + + delete unary; + delete s; + delete info; + + return r; +}