diff --git a/.github/workflows/hydra-ci.yml b/.github/workflows/hydra-ci.yml new file mode 100644 index 00000000..f2d53b7f --- /dev/null +++ b/.github/workflows/hydra-ci.yml @@ -0,0 +1,123 @@ +name: Hydra CUDA CI + +on: + push: + branches: [ '**' ] + tags: [ 'v*.*.*' ] + pull_request: + branches: [ main ] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - os: Windows + runner: self-hosted + cuda_arch: "89;90" + config: Release + # - os: Linux + # runner: ubuntu-latest + # cuda_arch: "89;90" + # config: Release + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed for GitVersion + + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0.10.2 + with: + versionSpec: '5.x' + + - name: Determine version + id: gitversion + uses: gittools/actions/gitversion/execute@v0.10.2 + + - name: Print GitVersion Info + run: echo "Calculated version is ${{ steps.gitversion.outputs.semVer }}" + + - name: Patch vcpkg files + run: > + python scripts/update-vcpkg-files.py + ${{ steps.gitversion.outputs.VersionSourceSha }} + ${{ steps.gitversion.outputs.semVer }} + + - name: Install CUDA Toolkit (Linux only) + if: matrix.os == 'Linux' + uses: Jimver/cuda-toolkit@v0.2.11 + # with: + # cuda: '12.3.2' + + - name: Configure CMake + run: > + cmake -S . -B build + -DGITVERSION_SEMVER="${{ steps.gitversion.outputs.semVer }}" + -DGITVERSION_MAJOR="${{ steps.gitversion.outputs.major }}" + -DGITVERSION_MINOR="${{ steps.gitversion.outputs.minor }}" + -DGITVERSION_PATCH="${{ steps.gitversion.outputs.patch }}" + -DCMAKE_CUDA_ARCHITECTURES="${{ matrix.cuda_arch }}" + -DCMAKE_BUILD_TYPE=${{ matrix.config }} + + - name: Build HydraCudaStatic + run: cmake --build build --target HydraCudaStatic --config ${{ matrix.config }} --parallel + + - name: Build MEX bindings (optional) + run: cmake --build build --target Mex --config ${{ matrix.config }} --parallel + continue-on-error: true + + - name: Build Python bindings (optional) + run: cmake --build build --target Python --config ${{ matrix.config }} --parallel + continue-on-error: true + + - name: Collect artifacts (Windows) + if: matrix.os == 'Windows' + run: | + mkdir -p staging/${{ matrix.os }} + cp -r build/src/c/Cuda/*.{dll,lib,pdb} staging/${{ matrix.os }} 2> $null || true + cp -r build/src/c/Python/*.pyd staging/${{ matrix.os }} 2> $null || true + cp -r build/src/c/Mex/*.mex* staging/${{ matrix.os }} 2> $null || true + + - name: Collect artifacts (Linux) + if: matrix.os != 'Windows' + run: | + mkdir -p staging/${{ matrix.os }} + cp -r build/src/c/Cuda/*.{so,a} staging/${{ matrix.os }} 2>/dev/null || true + cp -r build/src/c/Python/*.so staging/${{ matrix.os }} 2>/dev/null || true + cp -r build/src/c/Mex/*.mexa64 staging/${{ matrix.os }} 2>/dev/null || true + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: hydra-${{ matrix.os }}-${{ steps.gitversion.outputs.semVer }} + path: staging/${{ matrix.os }} + + - name: Install to staging dir + run: cmake --install build --prefix install-root + + - name: Upload installable artifacts + uses: actions/upload-artifact@v4 + with: + name: hydra_install_${{ matrix.os }} + path: install-root + + release: + name: Create GitHub Release + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - name: Download all build artifacts + uses: actions/download-artifact@v4 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + name: Hydra Release ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + files: | + hydra-*/** \ No newline at end of file diff --git a/.gitversion.yml b/.gitversion.yml new file mode 100644 index 00000000..85be8aa5 --- /dev/null +++ b/.gitversion.yml @@ -0,0 +1,31 @@ +mode: Mainline +branches: + main: + regex: ^main$ + increment: Minor + prevent-increment-of-merged-branch-version: true + is-release-branch: true + tag: '' + develop: + regex: ^develop$ + increment: Patch + tag: '' + feature: + regex: ^features?[/-] + increment: Inherit + tag: useBranchName + hotfix: + regex: ^hotfix(es)?[/-] + increment: Patch + tag: useBranchName + release: + regex: ^releases?[/-] + increment: None + tag: 'rc' + pull-request: + tag: 'pr' + increment: Inherit +ignore: + sha: [] +commit-message-incrementing: Enabled +merge-message-strategy: MergeMessage diff --git a/CMakeLists.txt b/CMakeLists.txt index acb80ca2..a0858b22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,126 @@ cmake_minimum_required(VERSION 3.22) -project(HydraImageProcessor LANGUAGES C CXX CUDA) +# Project metadata +project(HydraImageProcessor VERSION 0.0.0 LANGUAGES C CXX CUDA) -set(HYDRA_MODULE_NAME "HIP") +# =========================== +# GitVersioning (optional defaults for local builds) +# =========================== +if(NOT DEFINED GITVERSION_SEMVER) + set(GITVERSION_SEMVER "0.0.0-dev") +endif() +if(NOT DEFINED GITVERSION_MAJOR) + set(GITVERSION_MAJOR 0) +endif() +if(NOT DEFINED GITVERSION_MINOR) + set(GITVERSION_MINOR 0) +endif() +if(NOT DEFINED GITVERSION_PATCH) + set(GITVERSION_PATCH 0) +endif() + +# =========================== +# Version Header Generation +# =========================== +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/c/Version.h.in + ${CMAKE_BINARY_DIR}/generated/Version.h + @ONLY +) +include_directories(${CMAKE_BINARY_DIR}/generated) -# Use CMake's modern FindCUDAToolkit module +# =========================== +# CUDA Architecture Defaults +# =========================== +if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES "89;90;103;121" + CACHE STRING "CUDA architectures to build for" + FORCE) +endif() + +# =========================== +# Default Install Locations +# =========================== +include(GNUInstallDirs) + +# =========================== +# Dependencies +# =========================== find_package(CUDAToolkit REQUIRED) -find_package(OpenMP) +find_package(OpenMP REQUIRED COMPONENTS CXX) -# Find optional dependencies find_package(Matlab COMPONENTS MAIN_PROGRAM OPTIONAL_COMPONENTS MEX_COMPILER) -find_package(Python COMPONENTS Development NumPy OPTIONAL) - +# find_package(Python COMPONENTS Development NumPy OPTIONAL) -# Setup backend Hydra CUDA library (static) for CUDA building +# =========================== +# Subdirectories +# =========================== add_subdirectory(src/c/Cuda) add_subdirectory(src/c/test_back) -# Setup MEX interface if Matlab was found if (Matlab_FOUND) - add_subdirectory(src/c/Mex) + add_subdirectory(src/c/Mex) endif() -# Setup Python interface if Python is found +# Try to help FindPython by injecting the environment path (for local mamba or CI conda) +message(STATUS "CONDA_PREFIX: $ENV{CONDA_PREFIX}") +if(DEFINED ENV{CONDA_PREFIX}) + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(Python_EXECUTABLE "$ENV{CONDA_PREFIX}/python.exe" CACHE FILEPATH "Python interpreter") + else() + set(Python_EXECUTABLE "$ENV{CONDA_PREFIX}/python" CACHE FILEPATH "Python interpreter") + endif() + list(PREPEND CMAKE_PREFIX_PATH "$ENV{CONDA_PREFIX}") + message(STATUS "Python: ${Python_EXECUTABLE}") + message(STATUS "Python Include Dir: ${Python_INCLUDE_DIRS}") +else() + message(STATUS "Using system Python interpreter") +endif() + +find_package(Python COMPONENTS Interpreter Development NumPy OPTIONAL) +# Manually set the Python library location if using conda/mamba on Windows +# if(WIN32 AND NOT Python_LIBRARIES) +# set(Python_LIBRARIES "$ENV{CONDA_PREFIX}/libs/python311.lib") +# endif() + +message(STATUS "Python: ${Python_EXECUTABLE}") +message(STATUS "Python Include Dir: ${Python_INCLUDE_DIRS}") +message(STATUS "Python Library: ${Python_LIBRARIES}") +message(STATUS "NumPy Include Dir: ${Python_NumPy_INCLUDE_DIRS}") + if (Python_FOUND) - add_subdirectory(src/c/Python) + add_subdirectory(src/c/Python) endif() + +# =========================== +# CMake Package Config +# =========================== +include(CMakePackageConfigHelpers) + +# Generate hydra-config.cmake +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/hydra-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/hydra-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Hydra +) + +# Generate hydra-config-version.cmake +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/hydra-config-version.cmake + VERSION ${GITVERSION_SEMVER} + COMPATIBILITY AnyNewerVersion +) + +# Install config files +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/hydra-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/hydra-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Hydra +) + +# Export all targets (defined in subdirs like Cuda) +install(EXPORT HydraTargets + FILE HydraTargets.cmake + NAMESPACE Hydra:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Hydra +) diff --git a/license.txt b/LICENSE similarity index 100% rename from license.txt rename to LICENSE diff --git a/cmake/hydra-config.cmake.in b/cmake/hydra-config.cmake.in new file mode 100644 index 00000000..a71df40e --- /dev/null +++ b/cmake/hydra-config.cmake.in @@ -0,0 +1,4 @@ +# hydra-config.cmake.in +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/HydraTargets.cmake") diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..64afb691 --- /dev/null +++ b/environment.yml @@ -0,0 +1,7 @@ +name: hydra-build +channels: + - conda-forge +dependencies: + - python=3.11 + - numpy + - cmake diff --git a/ports/hydra/portfile.cmake b/ports/hydra/portfile.cmake new file mode 100644 index 00000000..98cc5243 --- /dev/null +++ b/ports/hydra/portfile.cmake @@ -0,0 +1,20 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO ericwait/hydra-image-processor + REF v${GITVERSION_SEMVER} # you can pass this in via `cmake -D` + SHA512 0 # This must be updated + HEAD_REF main +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + PREFER_NINJA +) + +vcpkg_cmake_build() + +vcpkg_cmake_install() + +vcpkg_cmake_config_fixup() + +file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) \ No newline at end of file diff --git a/ports/hydra/vcpkg.json b/ports/hydra/vcpkg.json new file mode 100644 index 00000000..6d205067 --- /dev/null +++ b/ports/hydra/vcpkg.json @@ -0,0 +1,12 @@ +{ + "name": "hydra", + "version": "0.1.0", + "description": "Hydra Image Processor: CUDA-accelerated image analysis backend.", + "homepage": "https://github.com/ericwait/hydra-image-processor", + "license": "BSD-3-Clause", + "dependencies": [ + "cuda", + "vcpkg-cmake", + "vcpkg-cmake-config" + ] +} diff --git a/scripts/update-vcpkg-files.py b/scripts/update-vcpkg-files.py new file mode 100644 index 00000000..e73b1b66 --- /dev/null +++ b/scripts/update-vcpkg-files.py @@ -0,0 +1,39 @@ +import json, hashlib, urllib.request, sys + +ref = sys.argv[1] # e.g., a tag, branch, or commit SHA +version = sys.argv[2] # e.g., "3.15.1-vcpkg.1" + +# Construct GitHub tarball URL for the given ref +tarball_url = f"https://github.com/ericwait/hydra-image-processor/archive/{ref}.tar.gz" + +# Download the tarball +print(f"Downloading tarball from: {tarball_url}") +tarball_data = urllib.request.urlopen(tarball_url).read() + +# Compute SHA512 +sha512 = hashlib.sha512(tarball_data).hexdigest() +print(f"Computed SHA512: {sha512}") + +# Patch vcpkg.json +with open("ports/hydra/vcpkg.json", "r") as f: + vcpkg = json.load(f) + +vcpkg["version"] = version + +with open("ports/hydra/vcpkg.json", "w") as f: + json.dump(vcpkg, f, indent=2) + f.write("\n") + +# Patch portfile.cmake +portfile_path = "ports/hydra/portfile.cmake" +with open(portfile_path, "r") as f: + lines = f.readlines() + +with open(portfile_path, "w") as f: + for line in lines: + if line.strip().startswith("REF "): + f.write(f' REF {ref}\n') + elif line.strip().startswith("SHA512 "): + f.write(f' SHA512 {sha512}\n') + else: + f.write(line) diff --git a/src/MATLAB/+HIP/CheckConfig.m b/src/MATLAB/+HIP/CheckConfig.m index 41969ef4..ffa6c881 100644 --- a/src/MATLAB/+HIP/CheckConfig.m +++ b/src/MATLAB/+HIP/CheckConfig.m @@ -7,5 +7,6 @@ [hydraConfig] = HIP.Cuda.CheckConfig(); catch errMsg warning(errMsg.message); + [hydraConfig] = HIP.Local.CheckConfig(); end end diff --git a/src/c/Cuda/CMakeLists.txt b/src/c/Cuda/CMakeLists.txt index cf419dd4..463a461a 100644 --- a/src/c/Cuda/CMakeLists.txt +++ b/src/c/Cuda/CMakeLists.txt @@ -1,16 +1,26 @@ -# Set a variable to turn on/off PROCESS_MUTEX support +cmake_minimum_required(VERSION 3.22) + +project(HydraCuda LANGUAGES CXX CUDA) + +# =========================== +# Options +# =========================== option(USE_PROCESS_MUTEX "Use process-level mutex to guard GPU calls" OFF) -# Common settings for both libraries -set(CUDA_ARCHITECTURES "52;61;70;75;86;89") +# =========================== +# Common Includes +# =========================== set(COMMON_INCLUDES $ - $ - $ + $ + $ ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ) -set(COMMON_HEADERS +# =========================== +# Header Files (full paths + validation) +# =========================== +set(HEADER_NAMES BufferConversions.h CHelpers.h CommandLineProgress.h @@ -26,6 +36,17 @@ set(COMMON_HEADERS Vec.h ) +set(COMMON_HEADERS "") +foreach(hdr IN LISTS HEADER_NAMES) + list(APPEND COMMON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${hdr}) + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${hdr}) + message(FATAL_ERROR "Missing header: ${hdr}") + endif() +endforeach() + +# =========================== +# Source Files +# =========================== set(COMMON_CUDA_SOURCES CudaAddTwoImages.cuh CudaClosure.cuh @@ -75,52 +96,67 @@ set(COMMON_CPP_SOURCES KernelIterator.cu ) -# Determine the OpenMP flag based on the compiler -if(MSVC) - message(STATUS "MSVC compiler detected") - set(OPENMP_FLAG "/openmp") - # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -Xcompiler=/openmp") -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - message(STATUS "GCC/Clang compiler detected") - set(OPENMP_FLAG "-fopenmp") - # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -Xcompiler=-fopenmp") -else() - message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}") -endif() - -# Function to set up both static and shared CUDA libraries -function(setup_cuda_library LIB_NAME STATIC_OR_SHARED) - add_library(${LIB_NAME} ${STATIC_OR_SHARED} "") +# =========================== +# OpenMP Setup +# =========================== +find_package(OpenMP REQUIRED COMPONENTS CXX) + +# =========================== +# CUDA Library Builder Function +# =========================== +function(setup_cuda_library LIB_NAME LIB_TYPE) + add_library(${LIB_NAME} ${LIB_TYPE}) target_compile_features(${LIB_NAME} PRIVATE cxx_std_17) + target_include_directories(${LIB_NAME} PUBLIC ${COMMON_INCLUDES} ) - - # Link OpenMP libraries - target_link_libraries(${LIB_NAME} PRIVATE CUDA::cudart_static PRIVATE OpenMP::OpenMP_CXX) - - # Set CUDA-specific compile options - # Pass the OpenMP flag to the host compiler via nvcc + + target_sources(${LIB_NAME} + PRIVATE ${COMMON_CPP_SOURCES} ${COMMON_CUDA_SOURCES} + ) + + target_link_libraries(${LIB_NAME} + PRIVATE CUDA::cudart_static + PRIVATE OpenMP::OpenMP_CXX + ) + target_compile_options(${LIB_NAME} PRIVATE - # $<$:${CUDA_NVCC_FLAGS}> - $<$:-Xcompiler=${OPENMP_FLAG}> + $<$:-Xcompiler=${OpenMP_CXX_FLAGS}> ) set_target_properties(${LIB_NAME} PROPERTIES - CUDA_STANDARD 11 CUDA_SEPARABLE_COMPILATION ON CUDA_RESOLVE_DEVICE_SYMBOLS ON - CUDA_ARCHITECTURES "${CUDA_ARCHITECTURES}" POSITION_INDEPENDENT_CODE ON ) - - target_sources(${LIB_NAME} - PUBLIC ${COMMON_HEADERS} - PRIVATE ${COMMON_CUDA_SOURCES} ${COMMON_CPP_SOURCES} - ) endfunction() -# Setup static and shared libraries +# =========================== +# Build Targets +# =========================== setup_cuda_library(HydraCudaStatic STATIC) # setup_cuda_library(HydraCuda SHARED) + +# =========================== +# Installation +# =========================== +install(FILES + ${COMMON_HEADERS} + DESTINATION include/hydra +) + +install(TARGETS HydraCudaStatic + EXPORT HydraTargets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) + +install(EXPORT HydraTargets + FILE HydraTargets.cmake + NAMESPACE Hydra:: + DESTINATION lib/cmake/Hydra +) diff --git a/src/c/Cuda/CudaAddTwoImages.cuh b/src/c/Cuda/CudaAddTwoImages.cuh index ef8df265..9e8c1494 100644 --- a/src/c/Cuda/CudaAddTwoImages.cuh +++ b/src/c/Cuda/CudaAddTwoImages.cuh @@ -59,10 +59,10 @@ void cAddTwoImages(ImageView imageIn1, ImageView ima for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn1, deviceIn1.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); if (!chunks[i].sendROI(imageIn2, deviceIn2.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceIn1.setAllDims(chunks[i].getFullChunkSize()); deviceIn2.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaClosure.cuh b/src/c/Cuda/CudaClosure.cuh index b17edc44..e90bbeee 100644 --- a/src/c/Cuda/CudaClosure.cuh +++ b/src/c/Cuda/CudaClosure.cuh @@ -44,7 +44,7 @@ void cClosure(ImageView imageIn, ImageView imageOut, for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaElementWiseDifference.cuh b/src/c/Cuda/CudaElementWiseDifference.cuh index 723114a6..98a89ccc 100644 --- a/src/c/Cuda/CudaElementWiseDifference.cuh +++ b/src/c/Cuda/CudaElementWiseDifference.cuh @@ -59,9 +59,9 @@ void cElementWiseDifference(ImageView image1In, ImageView imageIn, ImageView imageOut, I for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaGaussian.cuh b/src/c/Cuda/CudaGaussian.cuh index 50fd49b3..c9645310 100644 --- a/src/c/Cuda/CudaGaussian.cuh +++ b/src/c/Cuda/CudaGaussian.cuh @@ -50,7 +50,7 @@ void cGaussian(ImageView imageIn, ImageView imageOut, for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaHighPassFilter.cuh b/src/c/Cuda/CudaHighPassFilter.cuh index 1c201280..16164f96 100644 --- a/src/c/Cuda/CudaHighPassFilter.cuh +++ b/src/c/Cuda/CudaHighPassFilter.cuh @@ -51,7 +51,7 @@ void cHighPassFilter(ImageView imageIn, ImageView ima for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImagesIn.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImagesIn.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaIdentityFilter.cuh b/src/c/Cuda/CudaIdentityFilter.cuh index 3e5be064..55dbd43b 100644 --- a/src/c/Cuda/CudaIdentityFilter.cuh +++ b/src/c/Cuda/CudaIdentityFilter.cuh @@ -55,7 +55,7 @@ void cIdentityFilter(ImageView imageIn, ImageView ima for ( int i = CUDA_IDX; i < chunks.size(); i += N_THREADS ) { if ( !chunks[i].sendROI(imageIn, deviceImages.getCurBuffer()) ) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaLoG.cuh b/src/c/Cuda/CudaLoG.cuh index 446e417e..af3127ac 100644 --- a/src/c/Cuda/CudaLoG.cuh +++ b/src/c/Cuda/CudaLoG.cuh @@ -69,7 +69,7 @@ void cLoG(ImageView imageIn, ImageView imageOut, Vec if (sigmas.x!=0) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); cudaMultiplySumBias<<> > (*(deviceImages.getCurBuffer()), *(deviceImages.getNextBuffer()), constLoGKernelMem_x, MIN_VAL, MAX_VAL, constGausKernelMem_x, true); deviceImages.incrementBuffer(); @@ -91,7 +91,7 @@ void cLoG(ImageView imageIn, ImageView imageOut, Vec if (sigmas.y!=0) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); if (sigmas.x!=0) { @@ -113,7 +113,7 @@ void cLoG(ImageView imageIn, ImageView imageOut, Vec if (sigmas.z!=0) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); if (sigmas.x!=0) { diff --git a/src/c/Cuda/CudaMaxFilter.cuh b/src/c/Cuda/CudaMaxFilter.cuh index c72e9811..eac13819 100644 --- a/src/c/Cuda/CudaMaxFilter.cuh +++ b/src/c/Cuda/CudaMaxFilter.cuh @@ -71,7 +71,7 @@ void cMaxFilter(ImageView imageIn, ImageView imageOut for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMeanAndVariance.cuh b/src/c/Cuda/CudaMeanAndVariance.cuh index ba088cdc..95c3871a 100644 --- a/src/c/Cuda/CudaMeanAndVariance.cuh +++ b/src/c/Cuda/CudaMeanAndVariance.cuh @@ -129,7 +129,7 @@ void cMeanAndVariance(ImageView imageIn, ImageView mu for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMeanFilter.cuh b/src/c/Cuda/CudaMeanFilter.cuh index 1ada5590..434ee963 100644 --- a/src/c/Cuda/CudaMeanFilter.cuh +++ b/src/c/Cuda/CudaMeanFilter.cuh @@ -60,7 +60,7 @@ void cMeanFilter(ImageView imageIn, ImageView imageOu for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMedianFilter.cuh b/src/c/Cuda/CudaMedianFilter.cuh index d5343301..e89353b3 100644 --- a/src/c/Cuda/CudaMedianFilter.cuh +++ b/src/c/Cuda/CudaMedianFilter.cuh @@ -160,7 +160,7 @@ void cMedianFilter(ImageView imageIn, ImageView image for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMinFilter.cuh b/src/c/Cuda/CudaMinFilter.cuh index 498602e9..207c893c 100644 --- a/src/c/Cuda/CudaMinFilter.cuh +++ b/src/c/Cuda/CudaMinFilter.cuh @@ -71,7 +71,7 @@ void cMinFilter(ImageView imageIn, ImageView imageOut for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMinMax.cuh b/src/c/Cuda/CudaMinMax.cuh index 059adbf7..beaccfbf 100644 --- a/src/c/Cuda/CudaMinMax.cuh +++ b/src/c/Cuda/CudaMinMax.cuh @@ -204,7 +204,7 @@ void cMinMax(ImageView imageIn, PixelType& outMin, PixelType& outMax, for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaMultiplySum.cuh b/src/c/Cuda/CudaMultiplySum.cuh index 93fd8309..f8da1e8e 100644 --- a/src/c/Cuda/CudaMultiplySum.cuh +++ b/src/c/Cuda/CudaMultiplySum.cuh @@ -126,7 +126,7 @@ void cMultiplySum(ImageView imageIn, ImageView imageO for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaNLMeans.cuh b/src/c/Cuda/CudaNLMeans.cuh index cd3e124c..5b538c4b 100644 --- a/src/c/Cuda/CudaNLMeans.cuh +++ b/src/c/Cuda/CudaNLMeans.cuh @@ -175,7 +175,7 @@ void cNLMeans(ImageView imageIn, ImageView imageOut, for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaOpener.cuh b/src/c/Cuda/CudaOpener.cuh index 46a94be9..120a057b 100644 --- a/src/c/Cuda/CudaOpener.cuh +++ b/src/c/Cuda/CudaOpener.cuh @@ -44,7 +44,7 @@ void cOpener(ImageView imageIn, ImageView imageOut, I for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaStdFilter.cuh b/src/c/Cuda/CudaStdFilter.cuh index 2e4758da..6e10d6bb 100644 --- a/src/c/Cuda/CudaStdFilter.cuh +++ b/src/c/Cuda/CudaStdFilter.cuh @@ -61,7 +61,7 @@ void cStdFilter(ImageView imageIn, ImageView imageOut for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaSum.cuh b/src/c/Cuda/CudaSum.cuh index 02218b3a..2cbfd992 100644 --- a/src/c/Cuda/CudaSum.cuh +++ b/src/c/Cuda/CudaSum.cuh @@ -150,7 +150,7 @@ void cSum(ImageView imageIn, OutType& outVal, int device=-1) for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaVarFilter.cuh b/src/c/Cuda/CudaVarFilter.cuh index 282c4489..9b1f1954 100644 --- a/src/c/Cuda/CudaVarFilter.cuh +++ b/src/c/Cuda/CudaVarFilter.cuh @@ -60,7 +60,7 @@ void cVarFilter(ImageView imageIn, ImageView imageOut for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/CudaWienerFilter.cuh b/src/c/Cuda/CudaWienerFilter.cuh index cc9d8aa4..ec48a433 100644 --- a/src/c/Cuda/CudaWienerFilter.cuh +++ b/src/c/Cuda/CudaWienerFilter.cuh @@ -63,7 +63,7 @@ void cWienerFilter(ImageView imageIn, ImageView image for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Cuda/ImageChunk.h b/src/c/Cuda/ImageChunk.h index 9da0bd51..6db109a4 100644 --- a/src/c/Cuda/ImageChunk.h +++ b/src/c/Cuda/ImageChunk.h @@ -33,7 +33,7 @@ class ImageChunk } else { - std::runtime_error("This copy direction is not supported!"); + throw std::runtime_error("This copy direction is not supported!"); } } diff --git a/src/c/Cuda/Kernel.cu b/src/c/Cuda/Kernel.cu index 0d359288..8c4cad45 100644 --- a/src/c/Cuda/Kernel.cu +++ b/src/c/Cuda/Kernel.cu @@ -119,7 +119,7 @@ __host__ Kernel& Kernel::getOffsetCopy(Vec dimensions, std::size_t kernOut->init(); if (dims.product() < startOffset + dimensions.product()) - std::runtime_error("Trying to make a Kernel that access outside of the original memory space!"); + throw std::runtime_error("Trying to make a Kernel that access outside of the original memory space!"); kernOut->dims = dimensions; kernOut->cudaKernel = cudaKernel + startOffset; diff --git a/src/c/Cuda/_TemplateKernel.cuh b/src/c/Cuda/_TemplateKernel.cuh index 8f9dead2..9b523a12 100644 --- a/src/c/Cuda/_TemplateKernel.cuh +++ b/src/c/Cuda/_TemplateKernel.cuh @@ -72,7 +72,7 @@ void cFooFilter(ImageView imageIn, ImageView imageOut for (int i = CUDA_IDX; i < chunks.size(); i += N_THREADS) { if (!chunks[i].sendROI(imageIn, deviceImages.getCurBuffer())) - std::runtime_error("Error sending ROI to device!"); + throw std::runtime_error("Error sending ROI to device!"); deviceImages.setAllDims(chunks[i].getFullChunkSize()); diff --git a/src/c/Mex/CMakeLists.txt b/src/c/Mex/CMakeLists.txt index 3bbb5791..f420d97a 100644 --- a/src/c/Mex/CMakeLists.txt +++ b/src/c/Mex/CMakeLists.txt @@ -1,58 +1,65 @@ -# Setup MEX interface if Matlab was found -add_library(HydraMex MODULE "") +# =========================== +# Hydra MEX Interface +# =========================== +add_library(HydraMex MODULE) -# Set up custom post-build command to run MATLAB build scripts -include(autoBuildMex.cmake) - -# Require c++11 and set build definition to MEX_BUILD +# Require modern C++ and define for MEX target_compile_features(HydraMex PRIVATE cxx_std_17) target_compile_definitions(HydraMex PRIVATE MEX_BUILD) -# Set USE_PROCESS_MUTEX if cmake variable is set -if ( USE_PROCESS_MUTEX ) +if (USE_PROCESS_MUTEX) target_compile_definitions(HydraMex PRIVATE USE_PROCESS_MUTEX) endif() -# Link against Matlab libraries -target_link_libraries(HydraMex PRIVATE HydraCudaStatic ${Matlab_MX_LIBRARY} ${Matlab_MEX_LIBRARY}) +# Output name should be .mex +if (NOT Matlab_MEX_EXTENSION) + message(FATAL_ERROR "Matlab_MEX_EXTENSION is not set") +endif() -# Change output library name to .mex -set_target_properties(HydraMex - PROPERTIES - OUTPUT_NAME ${HYDRA_MODULE_NAME} - PREFIX "" - SUFFIX .${Matlab_MEX_EXTENSION} - POSITION_INDEPENDENT_CODE ON -) +message(STATUS "Matlab MEX extension: '${Matlab_MEX_EXTENSION}'") -# Setup Matlab include directories -target_include_directories(HydraMex PRIVATE ${Matlab_INCLUDE_DIRS}) +set_target_properties(HydraMex PROPERTIES + OUTPUT_NAME "${HYDRA_MODULE_NAME}" + PREFIX "" + SUFFIX ".${Matlab_MEX_EXTENSION}" + POSITION_INDEPENDENT_CODE ON +) -# Setup src include directories +# =========================== +# Includes and Linking +# =========================== target_include_directories(HydraMex PRIVATE + ${Matlab_INCLUDE_DIRS} $ $ $ ) -# Include module definition file for Windows -if ( WIN32 ) - target_sources(HydraMex PRIVATE HydraMex.def) -endif() +target_link_libraries(HydraMex + PRIVATE HydraCudaStatic + PRIVATE ${Matlab_MX_LIBRARY} + PRIVATE ${Matlab_MEX_LIBRARY} +) +# =========================== +# MEX Sources +# =========================== target_sources(HydraMex PRIVATE + # Local headers MexArgConverter.h MexIncludes.h MexTypes.h + # Meta programming helpers ../mph/const_string.h ../mph/integer_sequence.h ../mph/preproc_helper.h ../mph/qualifier_helpers.h ../mph/tuple_helpers.h + # Script command infrastructure ../ScriptCmds/ArgConverter.h ../ScriptCmds/GenCommands.h ../ScriptCmds/HydraConfig.h @@ -69,6 +76,7 @@ target_sources(HydraMex ../ScriptCmds/ScriptTraits.h ../ScriptCmds/ScriptTraitTfms.h + # Script command implementations ../ScriptCmds/Commands/ScrCmdCheckConfig.h ../ScriptCmds/Commands/ScrCmdClosure.h ../ScriptCmds/Commands/ScrCmdDeviceCount.h @@ -94,7 +102,18 @@ target_sources(HydraMex ../ScriptCmds/Commands/ScrCmdVarFilter.h ../ScriptCmds/Commands/ScrCmdWienerFilter.h + # Source files ../ScriptCmds/ScopedProcessMutex.cpp MexCommandModule.cpp HydraMexModule.cpp ) + +# Add Windows-specific .def file if needed +if (WIN32) + target_sources(HydraMex PRIVATE HydraMex.def) +endif() + +# =========================== +# Post-Build MATLAB Autogen +# =========================== +include(${CMAKE_CURRENT_LIST_DIR}/autoBuildMex.cmake) diff --git a/src/c/Python/CMakeLists.txt b/src/c/Python/CMakeLists.txt index 8c86a710..fb3e17c1 100644 --- a/src/c/Python/CMakeLists.txt +++ b/src/c/Python/CMakeLists.txt @@ -1,36 +1,34 @@ -# Setup MEX interface if Matlab was found -add_library(HydraPy MODULE "") +# =========================== +# Hydra Python Interface +# =========================== -# Require c++11 and set build definition to PY_BUILD -target_compile_features(HydraPy PRIVATE cxx_std_11) +add_library(HydraPy MODULE) + +# Require modern C++ and define for Python build +target_compile_features(HydraPy PRIVATE cxx_std_17) target_compile_definitions(HydraPy PRIVATE PY_BUILD) -# Set USE_PROCESS_MUTEX if cmake variable is set -if ( USE_PROCESS_MUTEX ) +if (USE_PROCESS_MUTEX) target_compile_definitions(HydraPy PRIVATE USE_PROCESS_MUTEX) endif() -# Link against Python and NumPy libraries -target_link_libraries(HydraPy PRIVATE HydraCudaStatic Python::Python Python::NumPy) - -# Change output library name to Hydra. -set_target_properties(HydraPy - PROPERTIES - OUTPUT_NAME ${HYDRA_MODULE_NAME} - PREFIX "" - POSITION_INDEPENDENT_CODE ON - LIBRARY_OUTPUT_DIRECTORY $ +# Output name should be Hydra. (e.g., .pyd on Windows) +set_target_properties(HydraPy PROPERTIES + OUTPUT_NAME ${HYDRA_MODULE_NAME} + PREFIX "" + POSITION_INDEPENDENT_CODE ON + LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/Python + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/Python ) -# On windows specifically set the suffix to .pyd -if ( WIN32 ) - set_target_properties(HydraPy PROPERTIES SUFFIX .pyd) +# On Windows, set the suffix to .pyd for Python modules +if (WIN32) + set_target_properties(HydraPy PROPERTIES SUFFIX ".pyd") endif() -# Setup Python/NumPy include directories -target_include_directories(HydraPy PRIVATE Python::Python Python::NumPy) - -# Setup src include directories +# =========================== +# Includes and Linking +# =========================== target_include_directories(HydraPy PRIVATE $ @@ -39,18 +37,30 @@ target_include_directories(HydraPy $ ) +target_link_libraries(HydraPy + PRIVATE HydraCudaStatic + PRIVATE Python::Python + PRIVATE Python::NumPy +) + +# =========================== +# Python Binding Sources +# =========================== target_sources(HydraPy PRIVATE + # Local headers PyArgConverter.h PyIncludes.h PyTypes.h + # Meta programming helpers ../mph/const_string.h ../mph/integer_sequence.h ../mph/preproc_helper.h ../mph/qualifier_helpers.h ../mph/tuple_helpers.h + # Script command infrastructure ../ScriptCmds/ArgConverter.h ../ScriptCmds/GenCommands.h ../ScriptCmds/HydraConfig.h @@ -67,6 +77,7 @@ target_sources(HydraPy ../ScriptCmds/ScriptTraits.h ../ScriptCmds/ScriptTraitTfms.h + # Script command implementations ../ScriptCmds/Commands/ScrCmdCheckConfig.h ../ScriptCmds/Commands/ScrCmdClosure.h ../ScriptCmds/Commands/ScrCmdDeviceCount.h @@ -92,6 +103,7 @@ target_sources(HydraPy ../ScriptCmds/Commands/ScrCmdVarFilter.h ../ScriptCmds/Commands/ScrCmdWienerFilter.h + # Source files ../ScriptCmds/ScopedProcessMutex.cpp HydraPyModule.cpp PyCommandModule.cpp diff --git a/src/c/Version.h.in b/src/c/Version.h.in new file mode 100644 index 00000000..9ef84f22 --- /dev/null +++ b/src/c/Version.h.in @@ -0,0 +1,6 @@ +# src/c/Version.h.in +#pragma once +#define HYDRA_VERSION "@GITVERSION_SEMVER@" +#define HYDRA_MAJOR @GITVERSION_MAJOR@ +#define HYDRA_MINOR @GITVERSION_MINOR@ +#define HYDRA_PATCH @GITVERSION_PATCH@