Repository: bbrister/SIFT3D Branch: master Commit: 6245b245b5d1 Files: 69 Total size: 591.7 KB Directory structure: gitextract_mi4zrg4r/ ├── .gitignore ├── CHANGES.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── SIFT3DConfig.cmake.in ├── SIFT3DConfigVersion.cmake.in ├── cli/ │ ├── CMakeLists.txt │ ├── denseSift3D.c │ ├── kpSift3D.c │ └── regSift3D.c ├── cmake/ │ ├── FindDCMTK.cmake │ ├── FindMingw.cmake │ ├── FindNIFTI.cmake │ ├── FindOpenMP.cmake │ └── SIFT3DPackage.cmake ├── doc/ │ ├── INSTALL_LINUX.md │ ├── INSTALL_MAC.md │ └── INSTALL_WINDOWS.md ├── examples/ │ ├── CMakeLists.txt │ ├── featuresC.c │ ├── featuresMatlab.m │ ├── ioC.c │ ├── ioMatlab.m │ ├── manualFeaturesMatlab.m │ ├── registerC.c │ └── registerMatlab.m ├── imutil/ │ ├── CMakeLists.txt │ ├── dicom.cpp │ ├── dicom.h │ ├── immacros.h │ ├── imtypes.h │ ├── imutil.c │ ├── imutil.h │ ├── kernels.cl │ ├── nifti.c │ ├── nifti.h │ └── templates/ │ ├── CMakeLists.txt │ └── sep_fir_3d.template ├── reg/ │ ├── CMakeLists.txt │ ├── reg.c │ └── reg.h ├── sift3d/ │ ├── CMakeLists.txt │ ├── sift.c │ └── sift.h └── wrappers/ ├── CMakeLists.txt └── matlab/ ├── CMakeLists.txt ├── README.md ├── Sift3DParser.m ├── Sift3DTest.m ├── checkUnits3D.m ├── detectSift3D.m ├── extractSift3D.m ├── imRead3D.m ├── imWrite3D.m ├── keypoint3D.m ├── matchSift3D.m ├── mexDetectSift3D.c ├── mexExtractSift3D.c ├── mexImRead3D.c ├── mexImWrite3D.c ├── mexMatchSift3D.c ├── mexOrientation3D.c ├── mexRegisterSift3D.c ├── mexutil.c ├── mexutil.h ├── orientation3D.m ├── registerSift3D.m └── setupSift3D.m ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ################################################################################ # Copyright (c) 2015 Blaine Rister et al., see LICENSE for details. ################################################################################ # Files to be ignored by git. ################################################################################ *.o *.nii *.DS_Store *.png build build/* debug debug/* scripts scripts/* *DEBUG* ================================================ FILE: CHANGES.md ================================================ # SIFT3D version history ## 1.0.0 * Initial release ## 1.1.0 * Added DICOM IO * Wrapped DICOM and NIFTI IO in C functions im_read and im_write * Added matlab wrappers keypoint3D, imRead3D and imWrite3D * Changed C function SIFT3D_Extract_Descriptors to SIFT3D_Extract_Descriptors and SIFT3D_Extract_Raw_Descriptors * Fixed various bugs, improving keypoint and descriptor accuracy ## 1.1.1 October 30, 2015 * Performance optimizations * Fixes to DICOM slice ordering (im_read, imRead3D) * Write more DICOM metadata (im_write, imWrite3D) * Corrected Mac build instructions ## 1.2.0 January 5, 2016 * Fixed Mac linking issues * Take real-world units into account in SIFT3D keypoints, descriptors * Added Matlab wrapper for image registration * Optionally resample input images prior to registration (regSift3D, register_SIFT3D_resample) * Write more NIFTI metadata (im_write, imWrite3D) * Changed C interface for descriptor extraction to disallow providing a custom Gaussian scale-space pyramid (SIFT3D_Extract_Descriptors) * Allow arbitrary output sizes in im_inv_transform * Minor bug fixes ## 1.3.0 March 28, 2016 * Add parameters for keypoint detection to Matlab interface * Add registration parameters to Matlab and CLI * Default in Matlab to faster version of image registration that avoids resampling * Improved error handling * Removed unused, faulty options * Print internal error messages to the Matlab command prompt ## 1.3.1 March 31, 2016 * Removed option to set the number of octaves, which was causing bugs ## 1.4.0 May 11, 2016 * Fixed bugs to improve keypoint and registration accuracy, especially concerning rotations * Fixed bug that caused crashing on some large images * Dramatically reduced memory consumption of keypoint matching * Refactored SIFT3D_nn_match_fb into SIFT3D_nn_match, as there is now no reason to prefer forward to forward-backward matching * Renamed headers macros.h and types.h to immacros.h and imtypes.h, respectively ## 1.4.1 May 25, 2016 * Removed keypoint refinement step, which did not improve the accuracy. ## 1.4.2 June 15, 2016 * Added multithreading with OpenMP * Improved keypoint matching speed ## 1.4.3 July 24, 2016 * Fixed CMake settings to allow linking to CMake targets without dependencies * Updated for newer versions of CMake, MinGW * Update package dependencies for Ubuntu 16.04 * Removed POSIX dependencies in header files to allow linking with Visual Studio * Added support for reading JPEG-compressed DICOM files * Ship both MS (.lib) and MinGW (.dll.a) import libraries on Windows * Ship with MinGW runtime libraries on Windows * Ship with OpenBLAS on Windows ## 1.4.4 September 13, 2017 * Add support for reading Dicom Segmentation Objects (DSOs) * Add the option to compile without DCMTK and nifticlib * Reading images no longer scales them (im_read, imRead3D) * Read DICOM CT scans in Hounsfield units (im_read, imRead3D) * Fix header includes for newer builds of MinGW (TDM-GCC) ## 1.4.5 January 17, 2018 * Fixed a bug in orientation assignment to improve the accuracy of SIFT3D descriptors. Thanks to KinMan for finding this bug. * Change the default value of corner_thresh parameter to 0.4, to get the same number of keypoints as before the fix * Add a new Matlab wrapper function to enable matching pre-computed descriptors (matchSift3D.m) * Compute the slice spacing of multi-file Dicom series, using this instead of the Slice Thickness metadata. Warn the user if the slice spacing differs from the slice thickness. Throw an error if the slice spacing is inconsistent between pairs of adjacent images. * Add keypoint octave indices to kpSift3D output. Thanks to v8korb for this suggestion. * Refactor RANSAC code for improved clarity and efficiency. Thanks to cslayers for this suggestion. ## 1.4.6 November 12, 2019 * Fix MEX file compilation for Matlab 2018b and newer * Improve Nifti-1 image reading to take into account slope and intercept * Convert PET scans to SUV * Read Dicom series which are stored in unusual orientations (e.g., Y-Z planes instead of X-Y). This is needed for reading 3D mammograms. * Support 4D Nifti files ================================================ FILE: CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. ################################################################################ # Top-level build file for SIFT3D. ################################################################################ include (ExternalProject) # Minimum CMake version cmake_minimum_required (VERSION 3.3) # Minimum C++ version set (CMAKE_CXX_STANDARD 11) set (CMAKE_CXX_STANDARD_REQUIRED ON) # Convert GNU archives to .lib files in Windows if (WIN32) set (CMAKE_GNUtoMS ON CACHE BOOL "Convert .dll.a files to .lib" ) endif () # Project name project (SIFT3D) # Project version set (SIFT3D_VERSION 1.4.6) # Default build type if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message (STATUS "Build type not specified. Defaulting to 'Release'.") set (CMAKE_BUILD_TYPE Release CACHE STRING "The type of build" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") endif () # Default settings for optional components if (WIN32) set (_BUILD_CLI OFF) else () set (_BUILD_CLI ON) endif() # Optional component variables set (BUILD_CLI ${_BUILD_CLI} CACHE BOOL "If ON, builds the command line interface") set (BUILD_EXAMPLES "ON" CACHE BOOL "If ON, builds the example programs") set (BUILD_PACKAGE "OFF" CACHE BOOL "If ON, builds the package generator") # Configurable paths set (INSTALL_LIB_DIR "lib/sift3d" CACHE PATH "Installation directory for libraries") set (INSTALL_BIN_DIR "bin" CACHE PATH "Installation directory for executables") set (INSTALL_INCLUDE_DIR "include/sift3d" CACHE PATH "Installation directory for header files") if (WIN32 AND NOT CYGWIN) set (DEFAULT_INSTALL_CMAKE_DIR "cmake") else () set (DEFAULT_INSTALL_CMAKE_DIR "lib/cmake/sift3d") endif () set (INSTALL_CMAKE_DIR ${DEFAULT_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") # Internal paths list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) set (LICENSE_FILE ${CMAKE_CURRENT_LIST_DIR}/LICENSE) set (README_FILE ${CMAKE_CURRENT_LIST_DIR}/README.md) # Configure RPATH options set (INSTALL_LIB_PATH "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}") set (CMAKE_SKIP_BUILD_RPATH OFF) set (CMAKE_BUILD_WITH_INSTALL_RPATH OFF) set (CMAKE_INSTALL_RPATH ${INSTALL_LIB_PATH}) set (CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) # Output directories set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # The name and location of the wrappers build and install directories set (WRAPPERS_DIR "wrappers") set (BUILD_WRAPPERS_DIR "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${WRAPPERS_DIR}") set (INSTALL_WRAPPERS_DIR "${INSTALL_LIB_DIR}/${WRAPPERS_DIR}") # Build flags set (DEBUG_FLAGS "-g -DVERBOSE") set (RELEASE_FLAGS "-O3 -DNDEBUG") # GCC-specific flags if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set (DEBUG_FLAGS "${DEBUG_FLAGS} -ggdb3") endif () # OS-specific build flags if (APPLE) # Enable undefined shared library symbols set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") # Use rpath set (CMAKE_MACOSX_RPATH ON) endif () set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${DEBUG_FLAGS}") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${DEBUG_FLAGS}") set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RELEASE_FLAGS}") set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELEASE_FLAGS}") # Find the system libraries if (WIN32) # m is not a separate library in Windows set (M_LIBRARY "") else () find_library (M_LIBRARY m) endif () find_package (ZLIB REQUIRED) # Try to find and use OpenMP find_package (OpenMP) if (OPENMP_FOUND) # Nothing extra needs to be done here. elseif (WITH_OpenMP) message (FATAL_ERROR "OpenMP not found. Please provide a compiler that " "supports OpenMP, or disable the OpenMP by setting " "the variable WITH_OpenMP to false.") else () message (STATUS "Failed to find OpenMP. Compiling without it.") endif () set (WITH_OpenMP ${OPENMP_FOUND} CACHE BOOL "If ON, parallelizes with OpenMP " "in release mode") if (WITH_OpenMP) set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${OpenMP_C_FLAGS}") set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${OpenMP_CXX_FLAGS}") endif () # Look for MATLAB in the default locations find_package (Matlab QUIET) # If not found, look for MATLAB in OS-specific locations if (NOT Matlab_FOUND) if (APPLE) set (Matlab_ROOT_DIR "/Applications/Matlab.app") find_package (Matlab QUIET) endif () endif () # Set up Matlab paths if (Matlab_FOUND) message (STATUS "Found Matlab.") elseif (BUILD_Matlab) message (FATAL_ERROR "Matlab not found. Please set the variable " "Matlab_ROOT_DIR to the location of your Matlab installation, " "or disable the Matlab toolbox by setting BUILD_Matlab to " "false.") else () message (STATUS "Matlab not found. The toolbox will not be built.") endif () set (BUILD_Matlab ${Matlab_FOUND} CACHE BOOL "If ON, builds the Matlab toolbox") if (BUILD_Matlab) message (STATUS "Building the Matlab toolbox.") # Set the name and location of the Matlab toolbox set (TOOLBOX_NAME "matlab") set (BUILD_TOOLBOX_DIR "${BUILD_WRAPPERS_DIR}/${TOOLBOX_NAME}") set (INSTALL_TOOLBOX_DIR "${INSTALL_WRAPPERS_DIR}/${TOOLBOX_NAME}" CACHE PATH "Installation directory for the Matlab toolbox") set (INSTALL_TOOLBOX_PATH "${CMAKE_INSTALL_PREFIX}/${INSTALL_TOOLBOX_DIR}") list (APPEND CMAKE_INSTALL_RPATH ${INSTALL_TOOLBOX_PATH}) # Get the path of the Matlab libraries get_filename_component (Matlab_LIBRARIES_PATH ${Matlab_MEX_LIBRARY} DIRECTORY) # Check for the MX library, which CMake cannot find on Windows if (NOT Matlab_MX_LIBRARY_FOUND) # Find Matlab's MX library find_library (Matlab_MX_LIBRARY mx libmx PATHS ${Matlab_LIBRARIES_PATH} ) endif () # Find the Matlab LAPACK and BLAS libraries find_library (Matlab_MWLAPACK_LIBRARY mwlapack libmwlapack PATHS ${Matlab_LIBRARIES_PATH} ) find_library (Matlab_MWBLAS_LIBRARY mwblas libmwblas PATHS ${Matlab_LIBRARIES_PATH} ) endif () # Generate the package configuration files set (CMAKE_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/SIFT3DConfig.cmake) set (CMAKE_VERSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/SIFT3DConfigVersion.cmake) configure_file (SIFT3DConfig.cmake.in ${CMAKE_CONFIG_FILE} @ONLY ) configure_file (SIFT3DConfigVersion.cmake.in ${CMAKE_VERSION_FILE} @ONLY) # Install the package configuration files install (FILES ${CMAKE_CONFIG_FILE} ${CMAKE_VERSION_FILE} DESTINATION ${INSTALL_CMAKE_DIR}) # Install the targets file install (EXPORT SIFT3D-targets DESTINATION ${INSTALL_CMAKE_DIR}) # Mandatory source subdirectories add_subdirectory (imutil) add_subdirectory (sift3d) add_subdirectory (reg) add_subdirectory (wrappers) # Command line interface if (BUILD_CLI) add_subdirectory (cli) endif () # Examples if (BUILD_EXAMPLES) add_subdirectory (examples) endif () # Packager file if (BUILD_PACKAGE) include (SIFT3DPackage) endif () ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-2016 Blaine Rister et al. 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. ================================================ FILE: README.md ================================================ # SIFT3D Copyright (c) 2015-2019 Blaine Rister et al., see LICENSE for details. SIFT3D is an analogue of the scale-invariant feature transform (SIFT) for three-dimensional images. It leverages volumetric data and real-world units to detect keypoints and extract a robust description of their content. It can also perform 3D image registration by matching SIFT3D features and fitting geometric transformations with the RANSAC algorithm. All of this is implemented in a cross-platform C library, with wrappers for Matlab. SIFT3D includes imutil, a utility library for image processing and linear algebra. This library performs file IO in a variety of medical imaging formats, including DICOM and NIFTI. ## Contents This code creates the following executables: - kpSift3D - Extract keypoints and descriptors from a single image. - regSift3D - Extract matches and a geometric transformation from two images. and the following libraries: - libreg.so - Image registration from SIFT3D features - libsift3d.so - Extract and match SIFT3D features - libimutil.so - Utility library for image processing, regression and linear algebra. Includes IO functions for DICOM and NIFTI file formats. It also contains a Matlab toolbox for calling the library functions from Matlab scripts. See the README in /wrappers/matlab for more information. ## Installation instructions See doc/INSTALL_\.md for instructions on installing SIFT3D for your specific platform. ## Usage instructions For instructions on using the CLI, use the "--help" option, e.g. kpSift3D --help See /examples for sample programs using the C and Matlab APIs. The following sections describe how to link a program to the SIFT3D libraries. ### Linking to SIFT3D libraries with CMake SIFT3D exports a CMake package to the install directories. Here is an example of compiling a C program with SIFT3D from a CMake list. find_package (SIFT3D) # Find SIFT3D add_executable (helloWorld helloWorld.c) # Declare a target target_link_libraries (helloWorld PUBLIC ${SIFT3D_LIBRARIES}) # Link to the SIFT3D libraries if (WIN32) # Find the SIFT3D headers target_include_directories (helloWorld PUBLIC "${SIFT3D_DIR}/../${SIFT3D_INCLUDE_DIRS}") else() target_include_directories (helloWorld PUBLIC ${SIFT3D_INCLUDE_DIRS}) endif() ### Linking to SIFT3D libraries without CMake The header files and libraries are installed to "sift3d" subdirectories in your installation tree. On most systems, you will need to add these subdirectories to your include and linker search paths. You will also need to link to the dependencies listed below. - libimutil - requires linking to LAPACK, BLAS, and zlib. Linking to DCMTK and nifticlib are optional. - libsift3d - requires linking to imutil - libreg - requires linking to sift3d and imutil Information about the dependencies can be found in the installation instructions. *Note: On Windows systems, some of the dependencies are statically linked to the SIFT3D libraries. In this case, it suffices to link to the DLLs in the "bin" subdirectory of your installation.* ## Contact Please contact me at blaine@stanford.edu if you have any questions or concerns. If you would like to cite this work, please refer to the following paper: B. Rister, M. A. Horowitz and D. L. Rubin, "Volumetric Image Registration From Invariant Keypoints," in *IEEE Transactions on Image Processing*, vol. 26, no. 10, pp. 4900-4910, Oct. 2017. doi: 10.1109/TIP.2017.2722689 The paper and automatic citations are available [here](http://ieeexplore.ieee.org/document/7967757/citations). ================================================ FILE: SIFT3DConfig.cmake.in ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # SIFT3D package configuration file. Defines the following variables: # SIFT3D_INCLUDE_DIRS - Include directories # SIFT3D_LIBRARIES - Library targets ################################################################################ # Import the targets, if not already imported if (NOT TARGET reg) include (${CMAKE_CURRENT_LIST_DIR}/SIFT3D-targets.cmake) endif() # Get the libraries set (SIFT3D_LIBRARIES reg sift3D imutil) set (SIFT3D_INCLUDE_DIRS @INSTALL_INCLUDE_DIR@) ================================================ FILE: SIFT3DConfigVersion.cmake.in ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # SIFT3D package version file. ################################################################################ set (PACKAGE_VERSION @SIFT3D_VERSION@) # Check whether the requested PACKAGE_FIND_VERSION is compatible if ("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") set (PACKAGE_VERSION_COMPATIBLE FALSE) else () set (PACKAGE_VERSION_COMPATIBLE TRUE) if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") set (PACKAGE_VERSION_EXACT TRUE) endif () endif () ================================================ FILE: cli/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for the command line interfaces. ################################################################################ add_executable (denseSift3D denseSift3D.c) target_link_libraries(denseSift3D PUBLIC sift3D imutil) target_link_libraries(denseSift3D PRIVATE ${M_LIBRARY}) add_executable(kpSift3D kpSift3D.c) target_link_libraries(kpSift3D PUBLIC sift3D imutil) add_executable(regSift3D regSift3D.c) target_link_libraries(regSift3D PUBLIC reg sift3D imutil) install (TARGETS denseSift3D kpSift3D regSift3D RUNTIME DESTINATION ${INSTALL_BIN_DIR} LIBRARY DESTINATION ${INSTALL_LIB_DIR} ARCHIVE DESTINATION ${INSTALL_LIB_DIR}) ================================================ FILE: cli/denseSift3D.c ================================================ /* ----------------------------------------------------------------------------- * denseSift3d.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains a command-line tool to extract dense SIFT3D features from * an image. * ----------------------------------------------------------------------------- */ #include #include #include #include "immacros.h" #include "imutil.h" #include "sift.h" #define BUF_SIZE (1 << 10) /* The log tag */ const char tag[] = "denseSift3d"; /* The help message */ const char help_msg[] = "Usage: denseSift3D [input.nii] [descriptors%.nii] \n" "\n" "Extracts a dense gradient histogram image from the input file. The \n" "output is a set of 12 images, each representing a channel or \n" "histogram bin. The last '%' character in the output filename is \n" "replaced by the channel index.\n" "\n" "Supported image formats: \n" " .dcm (DICOM) \n" " .nii (nifti-1) \n" " .nii.gz (gzip-compressed nifti-1) \n" " directory containing .dcm files \n" "\n" "Example: \n" " denseSift3d in.nii.gz out%.nii.gz \n" "\n" "Upon completion, the output would be the following 12 images: \n" " -out0.nii.gz \n" " -out1.nii.gz \n" " ... \n" " -out11.nii.gz \n" "\n"; /* Print an error message. */ void err_msg(const char *msg) { SIFT3D_ERR("%s: %s \n" "Use \"denseSift3d --help\" for more information. \n", tag, msg); } /* Report an unexpected error. */ void err_msgu(const char *msg) { err_msg(msg); print_bug_msg(); } int main(int argc, char **argv) { char out_name[BUF_SIZE], chan_str[BUF_SIZE]; Image im, desc, chan; SIFT3D sift3d; char *in_path, *out_path, *marker; size_t len; int c, marker_pos; /* Parse the GNU standard options */ switch (parse_gnu(argc, argv)) { case SIFT3D_HELP: puts(help_msg); return 0; case SIFT3D_VERSION: return 0; } /* Parse the arguments */ if (argc < 3) { err_msg("Not enough arguments."); return 1; } else if (argc > 3) { err_msg("Too many arguments."); return 1; } in_path = argv[1]; out_path = argv[2]; /* Initialize data */ init_im(&im); init_im(&desc); init_im(&chan); if (init_SIFT3D(&sift3d)) { err_msgu("Failed to initialize SIFT3D data."); return 1; } /* Read the image */ if (im_read(in_path, &im)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to read input image \"%s\".", in_path); err_msg(msg); return 1; } /* Ensure the output file name has a % character */ if ((marker = strrchr(out_path, '%')) == NULL) { err_msg("output filename must contain '%'."); return 1; } marker_pos = marker - out_path; /* Get the output file name length */ len = strlen(out_path) + (int) ceil(log10((double) im.nc)) - 1; if (len > BUF_SIZE) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Ouput filename cannot exceed %d " "characters.", BUF_SIZE); err_msg(msg); return 1; } /* Extract the descriptors */ if (SIFT3D_extract_dense_descriptors(&sift3d, &im, &desc)) { err_msgu("Failed to extract descriptors."); return 1; } /* Write each channel as a separate image */ for (c = 0; c < desc.nc; c++) { /* Get the channel */ if (im_channel(&desc, &chan, c)) { err_msgu("Failed to extract the channel."); return 1; } /* Form the output file name */ out_name[0] = '\0'; snprintf(chan_str, BUF_SIZE, "%d", c); strncat(out_name, out_path, marker_pos); strcat(out_name, chan_str); strcat(out_name, marker + 1); /* Write the channel */ if (im_write(out_name, &chan)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write output image " "\"%s\".", out_name); err_msg(msg); return 1; } } return 0; } ================================================ FILE: cli/kpSift3D.c ================================================ /* ----------------------------------------------------------------------------- * kpSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains the CLI to extract SIFT3D keypoints and descriptors from * a single image. * ----------------------------------------------------------------------------- */ #include #include #include "immacros.h" #include "imutil.h" #include "sift.h" /* Options */ #define KEYS 'a' #define DESC 'b' #define DRAW 'c' /* Message buffer size */ #define BUF_SIZE 1024 /* Help message */ const char help_msg[] = "Usage: kpSift3D [image.nii] \n" "\n" "Detects SIFT3D keypoints and extracts their descriptors from an " "image.\n" "\n" "Example: \n" " kpSift3D --keys keys.csv --desc desc.csv image.nii \n" "\n" "Output options: \n" " --keys [filename] \n" " Specifies the output file name for the keypoints. \n" " Supported file formats: .csv, .csv.gz \n" " --desc [filename] \n" " Specifies the output file name for the descriptors. \n" " Supported file formats: .csv, .csv.gz \n" " --draw [filename] \n" " Draws the keypoints in image space. \n" " Supported file formats: .dcm, .nii, .nii.gz, directory \n" "At least one of the output options must be specified. \n" "\n"; /* Print an error message */ static void err_msg(const char *msg) { SIFT3D_ERR("kpSift3D: %s \n" "Use \"kpSift3D --help\" for more information. \n", msg); } /* Report an unexpected error. */ static void err_msgu(const char *msg) { err_msg(msg); print_bug_msg(); } /* CLI for 3D SIFT */ int main(int argc, char *argv[]) { Image im; SIFT3D sift3d; Keypoint_store kp; SIFT3D_Descriptor_store desc; char *im_path, *keys_path, *desc_path, *draw_path; int c, num_args; const struct option longopts[] = { {"keys", required_argument, NULL, KEYS}, {"desc", required_argument, NULL, DESC}, {"draw", required_argument, NULL, DRAW}, {0, 0, 0, 0} }; // Parse the GNU standard options switch (parse_gnu(argc, argv)) { case SIFT3D_HELP: puts(help_msg); print_opts_SIFT3D(); return 0; case SIFT3D_VERSION: return 0; case SIFT3D_FALSE: break; default: err_msgu("Unexpected return from parse_gnu \n"); return 1; } // Initialize the SIFT data if (init_SIFT3D(&sift3d)) { err_msgu("Failed to initialize SIFT data."); return 1; } // Parse the SIFT3D options and increment the argument list if ((argc = parse_args_SIFT3D(&sift3d, argc, argv, SIFT3D_FALSE)) < 0) return 1; // Parse the kpSift3d options opterr = 1; keys_path = desc_path = draw_path = NULL; while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { switch (c) { case KEYS: keys_path = optarg; break; case DESC: desc_path = optarg; break; case DRAW: draw_path = optarg; break; case '?': default: return 1; } } // Ensure we have at least one output if (keys_path == NULL && desc_path == NULL && draw_path == NULL) { err_msg("No outputs specified."); return 1; } // Parse the required arguments num_args = argc - optind; if (num_args < 1) { err_msg("Not enough arguments."); return 1; } else if (num_args > 1) { err_msg("Too many arguments."); return 1; } im_path = argv[optind]; // Initialize data init_Keypoint_store(&kp); init_SIFT3D_Descriptor_store(&desc); init_im(&im); // Read the image if (im_read(im_path, &im)) { err_msg("Could not read image."); return 1; } // Extract keypoints if (SIFT3D_detect_keypoints(&sift3d, &im, &kp)) { err_msgu("Failed to detect keypoints."); return 1; } // Optionally write the keypoints if (keys_path != NULL && write_Keypoint_store(keys_path, &kp)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the keypoints to " "\"%s\"", keys_path); err_msg(msg); return 1; } // Optionally extract descriptors if (desc_path != NULL) { // Extract descriptors if (SIFT3D_extract_descriptors(&sift3d, &kp,&desc)) { err_msgu("Failed to extract descriptors."); return 1; } // Write the descriptors if (write_SIFT3D_Descriptor_store(desc_path, &desc)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the " "descriptors to \"%s\"", desc_path); err_msg(msg); return 1; } } // Optionally draw the keypoints if (draw_path != NULL) { Image draw; Mat_rm keys; // Initialize intermediates init_im(&draw); if (init_Mat_rm(&keys, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) err_msgu("Failed to initialize keys matrix"); // Convert to matrices if (Keypoint_store_to_Mat_rm(&kp, &keys)) { err_msgu("Failed to convert the keypoints to " "a matrix."); return 1; } // Draw the points if (draw_points(&keys, SIFT3D_IM_GET_DIMS(&im), 1, &draw)) { err_msgu("Failed to draw the points."); return 1; } // Write the output if (im_write(draw_path, &draw)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to draw the keypoints " "to \"%s\"", draw_path); err_msg(msg); return 1; } // Clean up im_free(&draw); } return 0; } ================================================ FILE: cli/regSift3D.c ================================================ /* ----------------------------------------------------------------------------- * regSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains the CLI to detect and match SIFT3D features between a pair * of images, and register one image to another. * ----------------------------------------------------------------------------- */ #include #include #include #include #include "immacros.h" #include "imutil.h" #include "sift.h" #include "reg.h" /* Option tags */ #define MATCHES 'a' #define TRANSFORM 'b' #define WARPED 'c' #define CONCAT 'd' #define KEYS 'e' #define LINES 'f' #define NN_THRESH 'g' #define ERR_THRESH 'h' #define NUM_ITER 'l' #define TYPE 'm' #define RESAMPLE 'n' /* Message buffer size */ #define BUF_SIZE 1024 /* Internal parameters */ const interp_type interp = LINEAR; // Interpolation used for the warped image const tform_type type_default = AFFINE; // Default transformation type /* Print the help message */ static void print_help() { printf( "Usage: regSift3D [source.nii] [reference.nii] \n" "\n" "Matches SIFT3D features. \n" "\n" "Supported input formats: \n" " .nii (nifti-1) \n" " .nii.gz (gzip-compressed nifti-1) \n" "\n" "Example: \n" " regSift3D --nn_thresh 0.8 --matches matches.csv src.nii ref.nii \n" "\n" "Output options: \n" " --matches [filename] - The feature matches. \n" " Supported file formats: .csv, .csv.gz \n" " --transform [filename] - The transformation parameters. \n" " Supported file formats: .csv, .csv.gz \n" " --warped [filename] - The warped source image. \n" " Supported file formats: .dcm, .nii, .nii.gz, directory \n" " --concat [filename] - Concatenated images, with source on the left \n" " Supported file formats: .dcm, .nii, .nii.gz, directory \n" " --keys [filename] - Keypoints drawn in the concatenated image \n" " Supported file formats: .dcm, .nii, .nii.gz, directory \n" " --lines [filename] - Lines drawn between matching keypoints \n" " Supported file formats: .dcm, .nii, .nii.gz, directory \n" "At least one output option must be specified. \n" "\n" "Other options: \n" " --nn_thresh [value] - Matching threshold on the nearest neighbor \n" " ratio, in the interval (0, 1]. (default: %.2f) \n" " --err_thresh [value] - RANSAC inlier threshold, in the interval \n" " (0, inf). This is a threshold on the squared Euclidean \n" " distance in real-world units. (default: %.1f) \n" " --num_iter [value] - Number of RANSAC iterations. (default: %d) \n" " --type [value] - Type of transformation to be applied. \n" " Supported arguments: \"affine\" (default: affine) \n" " --resample - Internally resample the images to have the same \n" " physical resolution. This is slow. Use it when the images \n" " have very different resolutions, for example registering 5mm \n" " to 1mm slices. \n" "\n", SIFT3D_nn_thresh_default, SIFT3D_err_thresh_default, SIFT3D_num_iter_default); print_opts_SIFT3D(); } /* Print an error message */ static void err_msg(const char *msg) { SIFT3D_ERR("regSift3D: %s \n" "Use \"regSift3D --help\" for more information. \n", msg); } /* Report an unexpected error. */ static void err_msgu(const char *msg) { err_msg(msg); print_bug_msg(); } int main(int argc, char *argv[]) { Reg_SIFT3D reg; SIFT3D sift3d; Ransac ran; Image src, ref; Mat_rm match_src, match_ref; void *tform, *tform_arg; char *src_path, *ref_path, *warped_path, *match_path, *tform_path, *concat_path, *keys_path, *lines_path; tform_type type; int num_args, c, have_match, have_tform, resample; const struct option longopts[] = { {"matches", required_argument, NULL, MATCHES}, {"transform", required_argument, NULL, TRANSFORM}, {"warped", required_argument, NULL, WARPED}, {"concat", required_argument, NULL, CONCAT}, {"keys", required_argument, NULL, KEYS}, {"lines", required_argument, NULL, LINES}, {"nn_thresh", required_argument, NULL, NN_THRESH}, {"err_thresh", required_argument, NULL, ERR_THRESH}, {"num_iter", required_argument, NULL, NUM_ITER}, {"type", required_argument, NULL, TYPE}, {"resample", no_argument, NULL, RESAMPLE}, {0, 0, 0, 0} }; const char str_affine[] = "affine"; // Parse the GNU standard options switch (parse_gnu(argc, argv)) { case SIFT3D_HELP: print_help(); return 0; case SIFT3D_VERSION: return 0; case SIFT3D_FALSE: break; default: err_msgu("Unexpected return from parse_gnu."); return 1; } // Initialize the data init_im(&src); init_im(&ref); init_Reg_SIFT3D(®); init_Ransac(&ran); if (init_SIFT3D(&sift3d) || init_Mat_rm(&match_src, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&match_ref, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) { err_msgu("Failed basic initialization."); return 1; } // Initialize parameters to defaults type = type_default; // Parse the SIFT3D options if ((argc = parse_args_SIFT3D(&sift3d, argc, argv, SIFT3D_FALSE)) < 0) return 1; if (set_SIFT3D_Reg_SIFT3D(®, &sift3d) || set_Ransac_Reg_SIFT3D(®, &ran)) { err_msgu("Failed to save the SIFT3D or Ransac parameters."); return 1; } // Parse the remaining options opterr = 1; have_match = have_tform = resample = SIFT3D_FALSE; match_path = tform_path = warped_path = concat_path = keys_path = lines_path = NULL; while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { switch (c) { case MATCHES: match_path = optarg; have_match = SIFT3D_TRUE; break; case TRANSFORM: tform_path = optarg; have_tform = SIFT3D_TRUE; break; case WARPED: warped_path = optarg; have_tform = SIFT3D_TRUE; break; case CONCAT: concat_path = optarg; have_match = SIFT3D_TRUE; break; case KEYS: keys_path = optarg; have_match = SIFT3D_TRUE; break; case LINES: lines_path = optarg; have_match = SIFT3D_TRUE; break; case NN_THRESH: { const double nn_thresh = atof(optarg); if (set_nn_thresh_Reg_SIFT3D(®, nn_thresh)) { err_msg("Invalid value for nn_thresh."); return 1; } break; } case ERR_THRESH: { const double err_thresh = atof(optarg); if (set_err_thresh_Ransac(&ran, err_thresh)) { err_msg("Invalid value for err_thresh."); return 1; } break; } case NUM_ITER: { const int num_iter = atoi(optarg); if (set_num_iter_Ransac(&ran, num_iter)) { err_msg("Invalid value for num_iter."); return 1; } break; } case TYPE: if (!strcmp(optarg, str_affine)) { type = AFFINE; } else { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Unrecognized transformation type: %s", optarg); err_msg(msg); return 1; } break; case RESAMPLE: resample = SIFT3D_TRUE; break; case '?': default: return 1; } } // Ensure that at least one output was specified if (!have_match && !have_tform) { err_msg("No outputs were specified."); return 1; } // Parse the required arguments num_args = argc - optind; if (num_args < 2) { err_msg("Not enough arguments."); return 1; } else if (num_args > 2) { err_msg("Too many arguments."); return 1; } src_path = argv[optind]; ref_path = argv[optind + 1]; // Allocate memory for the transformation if ((tform = malloc(tform_type_get_size(type))) == NULL) { err_msg("Out of memory."); return 1; } // Initialize the transformation if (init_tform(tform, type)) return 1; // Read the images if (im_read(src_path, &src)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to read the source image " "\"%s\"", src_path); err_msg(msg); return 1; } if (im_read(ref_path, &ref)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to read the reference image " "\"%s\"", ref_path); err_msg(msg); return 1; } // Process the images tform_arg = have_tform ? tform : NULL; if (resample) { // Optionally register with resampling if (register_SIFT3D_resample(®, &src, &ref, LINEAR, tform_arg)) { err_msg("Failed to register the images with " "resampling. \n"); return 1; } } else { // Set the images if (set_src_Reg_SIFT3D(®, &src)) { err_msg("Failed to set the source image."); return 1; } if (set_ref_Reg_SIFT3D(®, &ref)) { err_msg("Failed to set the reference image."); return 1; } // Match the features, optionally registering the images if (register_SIFT3D(®, tform_arg)) { err_msg("Failed to register the images."); return 1; } } // Convert the matches to matrices if (get_matches_Reg_SIFT3D(®, &match_src, &match_ref)) { err_msgu("Failed to convert matches to coordinates."); return 1; } // Write the outputs if (match_path != NULL) { Mat_rm matches; // Initialize intermediates init_Mat_rm(&matches, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE); // Form a combined matrix for both sets of matches if (concat_Mat_rm(&match_src, &match_ref, &matches, 1)) { err_msgu("Failed to concatenate the matches."); return 1; } if (write_Mat_rm(match_path, &matches)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the matches " "\"%s\"", match_path); err_msg(msg); return 1; } // Clean up cleanup_Mat_rm(&matches); } if (tform_path != NULL && write_tform(tform_path, tform)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the transformation " "parameters \"%s\"", tform_path); err_msg(msg); return 1; } // Optionally warp the source image if (warped_path != NULL) { Image warped; // Initialize intermediates init_im(&warped); // Set the output to the reference image size if (im_copy_dims(&ref, &warped)) { err_msgu("Failed to resize the warped image."); return 1; } // Warp if (im_inv_transform(tform, &src, interp, SIFT3D_FALSE, &warped)) { err_msgu("Failed to warp the source image."); return 1; } // Write the warped image if (im_write(warped_path, &warped)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the warped " "image \"%s\"", warped_path); err_msg(msg); return 1; } // Clean up im_free(&warped); } // Optionally draw the matches if (concat_path != NULL || keys_path != NULL || lines_path != NULL) { Image concat, keys, lines; Mat_rm keys_src, keys_ref; // Set up the pointers for the images Image *const concat_arg = concat_path == NULL ? NULL : &concat; Image *const keys_arg = keys_path == NULL ? NULL : &keys; Image *const lines_arg = lines_path == NULL ? NULL : &lines; // Initialize intermediates init_im(&concat); init_im(&keys); init_im(&lines); if (init_Mat_rm(&keys_src, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&keys_ref, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) { err_msgu("Failed to initialize keypoint matrices."); return 1; } // Convert the keypoint coordinates to matrices if (SIFT3D_Descriptor_coords_to_Mat_rm(®.desc_src, &keys_src) || SIFT3D_Descriptor_coords_to_Mat_rm(®.desc_ref, &keys_ref)) { err_msgu("Failed to convert the keypoints to " "matrices."); return 1; } // Draw the matches if (draw_matches(&src, &ref, &keys_src, &keys_ref, &match_src, &match_ref, concat_arg, keys_arg, lines_arg)) { err_msgu("Failed to draw the matches."); return 1; } // Optionally write a concatenated image if (concat_path != NULL && im_write(concat_path, &concat)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the " "concatenated image \"%s\"", concat_path); err_msg(msg); return 1; } // Optionally write the keypoints if (keys_path != NULL && im_write(keys_path, &keys)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the keypoint " "image \"%s\"", concat_path); err_msg(msg); return 1; } // Optionally write the matches if (lines_path != NULL && im_write(lines_path, &lines)) { char msg[BUF_SIZE]; snprintf(msg, BUF_SIZE, "Failed to write the line " "image \"%s\"", concat_path); err_msg(msg); return 1; } // Clean up im_free(&concat); im_free(&keys); im_free(&lines); } return 0; } ================================================ FILE: cmake/FindDCMTK.cmake ================================================ #.rst: # FindDCMTK # --------- # # Find DCMTK libraries and applications # # The module defines the following variables:: # # DCMTK_INCLUDE_DIRS - Directories to include to use DCMTK # DCMTK_LIBRARIES - Files to link against to use DCMTK # DCMTK_FOUND - If false, don't try to use DCMTK # DCMTK_DIR - (optional) Source directory for DCMTK # # Compatibility # ^^^^^^^^^^^^^ # # This module is able to find a version of DCMTK that does or does not export # a *DCMTKConfig.cmake* file. It applies a two step process: # # * Step 1: Attempt to find DCMTK version providing a *DCMTKConfig.cmake* file. # * Step 2: If step 1 failed, rely on *FindDCMTK.cmake* to set `DCMTK_*` variables details below. # # # `Recent DCMTK # `_ # provides a *DCMTKConfig.cmake* :manual:`package configuration file # `. To exclusively use the package configuration file # (recommended when possible), pass the `NO_MODULE` option to # :command:`find_package`. For example, `find_package(DCMTK NO_MODULE)`. # This requires official DCMTK snapshot *3.6.1_20140617* or newer. # # # Until all clients update to the more recent DCMTK, build systems will need # to support different versions of DCMTK. # # On any given system, the following combinations of DCMTK versions could be # considered: # # +--------+---------------------+-----------------------+-------------------+ # | | SYSTEM DCMTK | LOCAL DCMTK | Supported ? | # +--------+---------------------+-----------------------+-------------------+ # | Case A | NA | [ ] DCMTKConfig | YES | # +--------+---------------------+-----------------------+-------------------+ # | Case B | NA | [X] DCMTKConfig | YES | # +--------+---------------------+-----------------------+-------------------+ # | Case C | [ ] DCMTKConfig | NA | YES | # +--------+---------------------+-----------------------+-------------------+ # | Case D | [X] DCMTKConfig | NA | YES | # +--------+---------------------+-----------------------+-------------------+ # | Case E | [ ] DCMTKConfig | [ ] DCMTKConfig | YES (*) | # +--------+---------------------+-----------------------+-------------------+ # | Case F | [X] DCMTKConfig | [ ] DCMTKConfig | NO | # +--------+---------------------+-----------------------+-------------------+ # | Case G | [ ] DCMTKConfig | [X] DCMTKConfig | YES | # +--------+---------------------+-----------------------+-------------------+ # | Case H | [X] DCMTKConfig | [X] DCMTKConfig | YES | # +--------+---------------------+-----------------------+-------------------+ # # (*) See Troubleshooting section. # # Legend: # # NA ...............: Means that no System or Local DCMTK is available # # [ ] DCMTKConfig ..: Means that the version of DCMTK does NOT export a DCMTKConfig.cmake file. # # [X] DCMTKConfig ..: Means that the version of DCMTK exports a DCMTKConfig.cmake file. # # # Troubleshooting # ^^^^^^^^^^^^^^^ # # What to do if my project finds a different version of DCMTK? # # Remove DCMTK entry from the CMake cache per :command:`find_package` # documentation. #============================================================================= # Copyright 2004-2009 Kitware, Inc. # Copyright 2009-2010 Mathieu Malaterre # Copyright 2010 Thomas Sondergaard # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) # # Written for VXL by Amitha Perera. # Upgraded for GDCM by Mathieu Malaterre. # Modified for EasyViz by Thomas Sondergaard. # set(_dcmtk_dir_description "The directory of DCMTK build or install tree.") # Ensure that DCMTK_DIR is set to a reasonable default value # so that DCMTK libraries can be found on a standard Unix distribution. # It also overwrite the value of DCMTK_DIR after this one has been # set by a successful discovery of DCMTK by the unpatched FindDCMTK.cmake module # distributed with CMake (as of 0167cea) if(NOT DCMTK_DIR OR DCMTK_DIR STREQUAL "/usr/include/dcmtk") set(DCMTK_DIR "/usr" CACHE PATH ${_dcmtk_dir_description} FORCE) endif() set(_SAVED_DCMTK_DIR ${DCMTK_DIR}) # # Step1: Attempt to find a version of DCMTK providing a DCMTKConfig.cmake file. # if(NOT DCMTK_FIND_QUIETLY) message(STATUS "Trying to find DCMTK expecting DCMTKConfig.cmake") endif() find_package(DCMTK QUIET NO_MODULE) if(DCMTK_FOUND AND NOT "x" STREQUAL "x${DCMTK_LIBRARIES}" AND NOT "x" STREQUAL "x${DCMTK_INCLUDE_DIRS}") if(NOT DCMTK_FIND_QUIETLY) message(STATUS "Trying to find DCMTK expecting DCMTKConfig.cmake - ok") endif() return() else() if(NOT DCMTK_FIND_QUIETLY) message(STATUS "Trying to find DCMTK expecting DCMTKConfig.cmake - failed") endif() endif() if(NOT DCMTK_FIND_QUIETLY) message(STATUS "Trying to find DCMTK relying on FindDCMTK.cmake") endif() # Restore the value reset by the previous call to 'find_package(DCMTK QUIET NO_MODULE)' set(DCMTK_DIR ${_SAVED_DCMTK_DIR} CACHE PATH ${_dcmtk_dir_description} FORCE) # # Step2: Attempt to find a version of DCMTK that does NOT provide a DCMTKConfig.cmake file. # # prefer DCMTK_DIR over default system paths like /usr/lib if(DCMTK_DIR) set(CMAKE_PREFIX_PATH ${DCMTK_DIR}/lib ${CMAKE_PREFIX_PATH}) # this is given to FIND_LIBRARY or FIND_PATH endif() # Find all libraries, store debug and release separately foreach(lib dcmpstat dcmsr dcmsign dcmtls dcmqrdb dcmnet dcmjpeg dcmimage dcmimgle dcmdata oflog ofstd ijg12 ijg16 ijg8 ) # Find Release libraries find_library(DCMTK_${lib}_LIBRARY_RELEASE ${lib} PATHS ${DCMTK_DIR}/${lib}/libsrc ${DCMTK_DIR}/${lib}/libsrc/Release ${DCMTK_DIR}/${lib}/Release ${DCMTK_DIR}/lib ${DCMTK_DIR}/lib/Release ${DCMTK_DIR}/dcmjpeg/lib${lib}/Release NO_DEFAULT_PATH ) # Find Debug libraries find_library(DCMTK_${lib}_LIBRARY_DEBUG ${lib}${DCMTK_CMAKE_DEBUG_POSTFIX} PATHS ${DCMTK_DIR}/${lib}/libsrc ${DCMTK_DIR}/${lib}/libsrc/Debug ${DCMTK_DIR}/${lib}/Debug ${DCMTK_DIR}/lib ${DCMTK_DIR}/lib/Debug ${DCMTK_DIR}/dcmjpeg/lib${lib}/Debug NO_DEFAULT_PATH ) mark_as_advanced(DCMTK_${lib}_LIBRARY_RELEASE) mark_as_advanced(DCMTK_${lib}_LIBRARY_DEBUG) # Add libraries to variable according to build type if(DCMTK_${lib}_LIBRARY_RELEASE) list(APPEND DCMTK_LIBRARIES optimized ${DCMTK_${lib}_LIBRARY_RELEASE}) endif() if(DCMTK_${lib}_LIBRARY_DEBUG) list(APPEND DCMTK_LIBRARIES debug ${DCMTK_${lib}_LIBRARY_DEBUG}) endif() endforeach() set(CMAKE_THREAD_LIBS_INIT) if(DCMTK_oflog_LIBRARY_RELEASE OR DCMTK_oflog_LIBRARY_DEBUG) # Hack - Not having a DCMTKConfig.cmake file to read the settings from, we will attempt to # find the library in all cases. # Ideally, pthread library should be discovered only if DCMTK_WITH_THREADS is enabled. set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads) endif() if(CMAKE_THREAD_LIBS_INIT) list(APPEND DCMTK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) endif() # # SPECIFIC CASE FOR DCMTK BUILD DIR as DCMTK_DIR # (as opposed to a DCMTK install dir) # Have to find the source directory. if(EXISTS ${DCMTK_DIR}/CMakeCache.txt) load_cache(${DCMTK_DIR} READ_WITH_PREFIX "EXT" DCMTK_SOURCE_DIR) if(NOT EXISTS ${EXTDCMTK_SOURCE_DIR}) message(FATAL_ERROR "DCMTK build directory references nonexistant DCMTK source directory ${EXTDCMTK_SOURCE_DIR}") endif() endif() set(DCMTK_config_TEST_HEADER osconfig.h) set(DCMTK_dcmdata_TEST_HEADER dctypes.h) set(DCMTK_dcmimage_TEST_HEADER dicoimg.h) set(DCMTK_dcmimgle_TEST_HEADER dcmimage.h) set(DCMTK_dcmjpeg_TEST_HEADER djdecode.h) set(DCMTK_dcmnet_TEST_HEADER assoc.h) set(DCMTK_dcmpstat_TEST_HEADER dcmpstat.h) set(DCMTK_dcmqrdb_TEST_HEADER dcmqrdba.h) set(DCMTK_dcmsign_TEST_HEADER sicert.h) set(DCMTK_dcmsr_TEST_HEADER dsrtree.h) set(DCMTK_dcmtls_TEST_HEADER tlslayer.h) set(DCMTK_ofstd_TEST_HEADER ofstdinc.h) set(DCMTK_oflog_TEST_HEADER oflog.h) set(DCMTK_dcmjpls_TEST_HEADER djlsutil.h) set(DCMTK_INCLUDE_DIR_NAMES) foreach(dir config dcmdata dcmimage dcmimgle dcmjpeg dcmjpls dcmnet dcmpstat dcmqrdb dcmsign dcmsr dcmtls ofstd oflog) if(EXTDCMTK_SOURCE_DIR) set(SOURCE_DIR_PATH ${EXTDCMTK_SOURCE_DIR}/${dir}/include/dcmtk/${dir}) endif() find_path(DCMTK_${dir}_INCLUDE_DIR ${DCMTK_${dir}_TEST_HEADER} PATHS ${DCMTK_DIR}/${dir}/include ${DCMTK_DIR}/${dir} ${DCMTK_DIR}/include/dcmtk/${dir} ${DCMTK_DIR}/${dir}/include/dcmtk/${dir} ${DCMTK_DIR}/include/${dir} ${SOURCE_DIR_PATH} ) mark_as_advanced(DCMTK_${dir}_INCLUDE_DIR) list(APPEND DCMTK_INCLUDE_DIR_NAMES DCMTK_${dir}_INCLUDE_DIR) if(DCMTK_${dir}_INCLUDE_DIR) # add the 'include' path so eg #include "dcmtk/dcmimgle/dcmimage.h" # works get_filename_component(_include ${DCMTK_${dir}_INCLUDE_DIR} PATH) get_filename_component(_include ${_include} PATH) list(APPEND DCMTK_INCLUDE_DIRS ${DCMTK_${dir}_INCLUDE_DIR} ${_include}) endif() endforeach() list(APPEND DCMTK_INCLUDE_DIRS ${DCMTK_DIR}/include) if(WIN32) list(APPEND DCMTK_LIBRARIES netapi32 wsock32) endif() if(DCMTK_ofstd_INCLUDE_DIR) get_filename_component(DCMTK_dcmtk_INCLUDE_DIR ${DCMTK_ofstd_INCLUDE_DIR} PATH CACHE) list(APPEND DCMTK_INCLUDE_DIRS ${DCMTK_dcmtk_INCLUDE_DIR}) mark_as_advanced(DCMTK_dcmtk_INCLUDE_DIR) endif() # Compatibility: This variable is deprecated set(DCMTK_INCLUDE_DIR ${DCMTK_INCLUDE_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(DCMTK REQUIRED_VARS ${DCMTK_INCLUDE_DIR_NAMES} DCMTK_LIBRARIES FAIL_MESSAGE "Please set DCMTK_DIR and re-run configure") # Workaround bug in packaging of DCMTK 3.6.0 on Debian. # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=637687 if(DCMTK_FOUND AND UNIX AND NOT APPLE) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_FLAGS ) set(CMAKE_REQUIRED_DEFINITIONS ) set(CMAKE_REQUIRED_INCLUDES ${DCMTK_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${DCMTK_LIBRARIES}) set(CMAKE_REQUIRED_QUIET ${DCMTK_FIND_QUIETLY}) check_cxx_source_compiles("#include \n#include \nint main(int,char*[]){return 0;}" DCMTK_HAVE_CONFIG_H_OPTIONAL ) if(NOT DCMTK_HAVE_CONFIG_H_OPTIONAL) set(DCMTK_DEFINITIONS "HAVE_CONFIG_H") endif() endif() if(NOT DCMTK_FIND_QUIETLY) message(STATUS "Trying to find DCMTK relying on FindDCMTK.cmake - ok") endif() ================================================ FILE: cmake/FindMingw.cmake ================================================ # FindNIFTI.cmake # # Finds the MinGW runtime dependencies: # # -libgcc # -libstdc++ # -libgfortran # -libquadmath # # The following variables will be exported: # MINGW_LIBRARIES - The dependencies # Standard MinGW locations set (MINGW_HINTS "C:/TDM-GCC/") set (MINGW_SUFFIXES "bin") # Get the names of the libraries, depending on 32- or 64-bit if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") message(STATUS "Using 64-bit MinGW...") set (MINGW_GCC_LIBRARY_NAME "libgcc_s_seh_64-1") set (MINGW_CXX_LIBRARY_NAME "libstdc++_64-6") set (MINGW_GFORTRAN_LIBRARY_NAME "libgfortran_64-3") set (MINGW_QUADMATH_LIBRARY_NAME "libquadmath_64-0") elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") message(STATUS "Using 32-bit MinGW...") set (MINGW_GCC_LIBRARY_NAME "libgcc_s_dw2-1") set (MINGW_CXX_LIBRARY_NAME "libstdc++-6") set (MINGW_GFORTRAN_LIBRARY_NAME "libgfortran-3") set (MINGW_QUADMATH_LIBRARY_NAME "libquadmath-0") else () message(FATAL_ERROR "Unrecognized byte width: ${CMAKE_SIZE_OF_VOID_P}") endif () find_library ( MINGW_GCC_LIBRARY NAMES ${MINGW_GCC_LIBRARY_NAME} HINTS ${MINGW_HINTS} PATH_SUFFIXES ${MINGW_SUFFIXES} DOC "MinGW C standard library" ) find_library ( MINGW_CXX_LIBRARY names ${MINGW_CXX_LIBRARY_NAME} HINTS ${MINGW_HINTS} PATH_SUFFIXES ${MINGW_SUFFIXES} DOC "MinGW C++ standard library" ) find_library ( MINGW_GFORTRAN_LIBRARY NAMES ${MINGW_GFORTRAN_LIBRARY_NAME} HINTS ${MINGW_HINTS} PATH_SUFFIXES ${MINGW_SUFFIXES} DOC "MinGW Fortran standard library" ) find_library ( MINGW_QUADMATH_LIBRARY NAMES ${MINGW_QUADMATH_LIBRARY_NAME} HINTS ${MINGW_HINTS} PATH_SUFFIXES ${MINGW_SUFFIXES} DOC "MinGW quadmath library" ) set (MINGW_LIBRARIES ${MINGW_GCC_LIBRARY} ${MINGW_CXX_LIBRARY} ${MINGW_GFORTRAN_LIBRARY} ${MINGW_QUADMATH_LIBRARY}) # handle the QUIETLY and REQUIRED arguments and set PNG_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(MINGW "Cannot find package MinGW." MINGW_GCC_LIBRARY MINGW_CXX_LIBRARY MINGW_GFORTRAN_LIBRARY MINGW_QUADMATH_LIBRARY ) # these variables are only visible in 'advanced mode' MARK_AS_ADVANCED( MINGW_GCC_LIBRARY MINGW_CXX_LIBRARY MINGW_GFORTRAN_LIBRARY MINGW_QUADMATH_LIBRARY ) ================================================ FILE: cmake/FindNIFTI.cmake ================================================ # - FindNIFTI.cmake # # Author: Thomas Proeger # Modified by Blaine Rister # # This cmake find module looks for the header files and libraries from the # 'libnifti1-dev' package. # # The following variables will be exported: # # NIFTI_INCLUDE_DIR - the directory that contains nifti1.h # NIFTI_NIFTICDF_LIBRARY - the libnifticdf library file # NIFTI_NIFTIIO_LIBRARY - the libniftiio library file # NIFTI_ZNZ_LIBRARY - the libznz library file # NIFTI_FOUND - TRUE if and only if ALL other variables have # correct values. # # INCLUDE directory FIND_PATH(NIFTI_INCLUDE_DIRS NAMES nifti1.h PATHS ${NIFTI_DIR} HINTS "C:/Program Files (x86)/NIFTI" "C:/Program Files/NIFTI" PATH_SUFFIXES include nifti include/nifti DOC "The include directory containing nifti1.h" ) # LIBRARY files FIND_LIBRARY(NIFTI_NIFTICDF_LIBRARY NAMES nifticdf PATHS ${NIFTI_DIR} HINTS "C:/Program Files (x86)/NIFTI" "C:/Program Files/NIFTI" PATH_SUFFIXES lib DOC "The library file libnifticdf.so" ) FIND_LIBRARY(NIFTI_NIFTIIO_LIBRARY NAMES niftiio PATHS ${NIFTI_DIR} HINTS "C:/Program Files (x86)/NIFTI" "C:/Program Files/NIFTI" PATH_SUFFIXES lib DOC "The library file libniftiiof.so" ) FIND_LIBRARY(NIFTI_ZNZ_LIBRARY NAMES znz PATHS ${NIFTI_DIR} HINTS "C:/Program Files (x86)/NIFTI" "C:/Program Files/NIFTI" PATH_SUFFIXES lib DOC "The library file libznz.so" ) # Allow the user to specify the path to the NIFTI installation get_filename_component (_NIFTI_DIR ${NIFTI_NIFTIIO_LIBRARY} DIRECTORY) set (NIFTI_DIR ${_NIFTI_DIR} CACHE PATH "The directory containing the NIFTI installation") # The combined NIFTI libraries set (NIFTI_LIBRARIES ${NIFTI_NIFTICDF_LIBRARY} ${NIFTI_NIFTIIO_LIBRARY} ${NIFTI_ZNZ_LIBRARY}) # handle the QUIETLY and REQUIRED arguments and set PNG_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(NIFTI "Cannot find package NIFTI. Did you install the package libnifti1-dev?" NIFTI_INCLUDE_DIRS NIFTI_NIFTICDF_LIBRARY NIFTI_NIFTIIO_LIBRARY NIFTI_ZNZ_LIBRARY ) # these variables are only visible in 'advanced mode' MARK_AS_ADVANCED(NIFTI_INCLUDE_DIRS NIFTI_NIFTICDF_LIBRARY NIFTI_NIFTIIO_LIBRARY NIFTI_ZNZ_LIBRARY ) ================================================ FILE: cmake/FindOpenMP.cmake ================================================ #.rst: # FindOpenMP # ---------- # # Finds OpenMP support # # This module can be used to detect OpenMP support in a compiler. If # the compiler supports OpenMP, the flags required to compile with # OpenMP support are returned in variables for the different languages. # The variables may be empty if the compiler does not need a special # flag to support OpenMP. # # The following variables are set: # # :: # # OpenMP_C_FLAGS - flags to add to the C compiler for OpenMP support # OpenMP_C_LIBRARIES - OpenMP C libraries to link against # OpenMP_CXX_FLAGS - flags to add to the CXX compiler for OpenMP support # OpenMP_CXX_LIBRARIES - OpenMP CXX libraries to link against # OpenMP_Fortran_FLAGS - flags to add to the Fortran compiler for OpenMP support # OpenMP_Fortran_LIBRARIES - OpenMP Fortran libraries to link against # OPENMP_FOUND - true if openmp is detected # # The following internal variables are set, if detected: # OpenMP_C_SPEC_DATE - specification date of OpenMP version of C compiler # OpenMP_CXX_SPEC_DATE - specification date of OpenMP version of CXX compiler # OpenMP_Fortran_SPEC_DATE - specification date of OpenMP version of Fortran compiler # # # # Supported compilers can be found at # http://openmp.org/wp/openmp-compilers/ # Specification dates for each version can be found at # http://openmp.org/wp/openmp-specifications/ #============================================================================= # Copyright 2009 Kitware, Inc. # Copyright 2008-2009 André Rigland Brodtkorb # Copyright 2012 Rolf Eike Beer # Copyright 2014 Nicolas Bock # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) set(_OPENMP_REQUIRED_VARS) set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) set(CMAKE_REQUIRED_QUIET ${OpenMP_FIND_QUIETLY}) function(_OPENMP_FLAG_CANDIDATES LANG) set(OpenMP_FLAG_CANDIDATES #Empty, if compiler automatically accepts openmp " " #GNU "-fopenmp" #Microsoft Visual Studio "/openmp" #Intel windows "-Qopenmp" #PathScale, Intel "-openmp" #Sun "-xopenmp" #HP "+Oopenmp" #IBM XL C/c++ "-qsmp" #Portland Group "-mp" ) set(OMP_FLAG_GNU "-fopenmp") set(OMP_FLAG_HP "+Oopenmp") if(WIN32) set(OMP_FLAG_Intel "-Qopenmp") elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Intel" AND "${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS "15.0.0.20140528") set(OMP_FLAG_Intel "-openmp") else() set(OMP_FLAG_Intel "-qopenmp") endif() set(OMP_FLAG_MSVC "/openmp") set(OMP_FLAG_PathScale "-openmp") set(OMP_FLAG_PGI "-mp") set(OMP_FLAG_SunPro "-xopenmp") set(OMP_FLAG_XL "-qsmp") set(OMP_FLAG_Cray " ") # Move the flag that matches the compiler to the head of the list, # this is faster and doesn't clutter the output that much. If that # flag doesn't work we will still try all. if(OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}) list(REMOVE_ITEM OpenMP_FLAG_CANDIDATES "${OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}}") list(INSERT OpenMP_FLAG_CANDIDATES 0 "${OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}}") endif() set(OpenMP_${LANG}_FLAG_CANDIDATES "${OpenMP_FLAG_CANDIDATES}" PARENT_SCOPE) endfunction() # sample openmp source code to test set(OpenMP_C_TEST_SOURCE " #include int main() { #ifdef _OPENMP return 0; #else breaks_on_purpose #endif } ") # same in Fortran set(OpenMP_Fortran_TEST_SOURCE " program test use omp_lib integer :: n n = omp_get_num_threads() end program test " ) set(OpenMP_C_CXX_CHECK_VERSION_SOURCE " #include #include const char ompver_str[] = { 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', 'P', '-', 'd', 'a', 't', 'e', '[', ('0' + ((_OPENMP/100000)%10)), ('0' + ((_OPENMP/10000)%10)), ('0' + ((_OPENMP/1000)%10)), ('0' + ((_OPENMP/100)%10)), ('0' + ((_OPENMP/10)%10)), ('0' + ((_OPENMP/1)%10)), ']', '\\0' }; int main(int argc, char *argv[]) { printf(\"%s\\n\", ompver_str); return 0; } ") set(OpenMP_Fortran_CHECK_VERSION_SOURCE " program omp_ver use omp_lib integer, parameter :: zero = ichar('0') integer, parameter :: ompv = openmp_version character, dimension(24), parameter :: ompver_str =& (/ 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', 'P', '-',& 'd', 'a', 't', 'e', '[',& char(zero + mod(ompv/100000, 10)),& char(zero + mod(ompv/10000, 10)),& char(zero + mod(ompv/1000, 10)),& char(zero + mod(ompv/100, 10)),& char(zero + mod(ompv/10, 10)),& char(zero + mod(ompv/1, 10)), ']' /) print *, ompver_str end program omp_ver ") function(_OPENMP_GET_SPEC_DATE LANG SPEC_DATE) set(WORK_DIR ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP) if("${LANG}" STREQUAL "C") set(SRC_FILE ${WORK_DIR}/ompver.c) file(WRITE ${SRC_FILE} "${OpenMP_C_CXX_CHECK_VERSION_SOURCE}") elseif("${LANG}" STREQUAL "CXX") set(SRC_FILE ${WORK_DIR}/ompver.cpp) file(WRITE ${SRC_FILE} "${OpenMP_C_CXX_CHECK_VERSION_SOURCE}") else() # ("${LANG}" STREQUAL "Fortran") set(SRC_FILE ${WORK_DIR}/ompver.f90) file(WRITE ${SRC_FILE} "${OpenMP_Fortran_CHECK_VERSION_SOURCE}") endif() set(BIN_FILE ${WORK_DIR}/ompver_${LANG}.bin) try_compile(OpenMP_TRY_COMPILE_RESULT ${CMAKE_BINARY_DIR} ${SRC_FILE} COMPILE_DEFINITIONS ${OpenMP_${LANG}_FLAGS} LINK_LIBRARIES "${OpenMP_${LANG}_LIBRARIES}" COPY_FILE ${BIN_FILE}) if(${OpenMP_TRY_COMPILE_RESULT}) file(STRINGS ${BIN_FILE} specstr LIMIT_COUNT 1 REGEX "INFO:OpenMP-date") set(regex_spec_date ".*INFO:OpenMP-date\\[0*([^]]*)\\].*") if("${specstr}" MATCHES "${regex_spec_date}") set(${SPEC_DATE} "${CMAKE_MATCH_1}" PARENT_SCOPE) endif() endif() endfunction() # check c compiler if(CMAKE_C_COMPILER_LOADED) # if these are set then do not try to find them again, # by avoiding any try_compiles for the flags if(OpenMP_C_FLAGS) unset(OpenMP_C_FLAG_CANDIDATES) else() _OPENMP_FLAG_CANDIDATES("C") include(CheckCSourceCompiles) endif() foreach(FLAG IN LISTS OpenMP_C_FLAG_CANDIDATES) set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "${FLAG}") unset(OpenMP_FLAG_DETECTED CACHE) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Try OpenMP C flag = [${FLAG}]") endif() check_c_source_compiles("${OpenMP_C_TEST_SOURCE}" OpenMP_FLAG_DETECTED) set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") if(OpenMP_FLAG_DETECTED) set(OpenMP_C_FLAGS_INTERNAL "${FLAG}") break() endif() endforeach() set(OpenMP_C_FLAGS "${OpenMP_C_FLAGS_INTERNAL}" CACHE STRING "C compiler flags for OpenMP parallization") list(APPEND _OPENMP_REQUIRED_VARS OpenMP_C_FLAGS) unset(OpenMP_C_FLAG_CANDIDATES) if ("x${CMAKE_C_COMPILER_ID}" STREQUAL "xMSVC") elseif (MINGW) # Get the names of the C libraries, depending on 32- or 64-bit if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") message(STATUS "Using 64-bit OpenMP...") set (OpenMP_C_LIBRARY_NAME "libgomp_64-1") elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") message(STATUS "Using 32-bit OpenMP...") set (OpenMP_C_LIBRARY_NAME "libgomp-1") else () message(FATAL_ERROR "Unrecognized byte width: ${CMAKE_SIZE_OF_VOID_P}") endif () # Find the OpenMP C library for MinGW find_library( OpenMP_C_LIBRARY NAMES ${OpenMP_C_LIBRARY_NAME} HINTS "C:/TDM-GCC" PATH_SUFFIXES "bin" DOC "The OpenMP C library" ) else () set (OpenMP_C_LIBRARY "${OpenMP_C_FLAGS}" CACHE STRING "C library for OpenMP") list (APPEND _OPENMP_REQUIRED_VARS OpenMP_C_LIBRARY) set (OpenMP_CXX_LIBRARIES OpenMP_C_LIBRARIES) endif() set (OpenMP_C_LIBRARIES "${OpenMP_C_LIBRARY}") if (NOT OpenMP_C_SPEC_DATE) _OPENMP_GET_SPEC_DATE("C" OpenMP_C_SPEC_DATE_INTERNAL) set(OpenMP_C_SPEC_DATE "${OpenMP_C_SPEC_DATE_INTERNAL}" CACHE INTERNAL "C compiler's OpenMP specification date") endif() endif() # check cxx compiler if(CMAKE_CXX_COMPILER_LOADED) # if these are set then do not try to find them again, # by avoiding any try_compiles for the flags if(OpenMP_CXX_FLAGS) unset(OpenMP_CXX_FLAG_CANDIDATES) else() _OPENMP_FLAG_CANDIDATES("CXX") include(CheckCXXSourceCompiles) # use the same source for CXX as C for now set(OpenMP_CXX_TEST_SOURCE ${OpenMP_C_TEST_SOURCE}) endif() foreach(FLAG IN LISTS OpenMP_CXX_FLAG_CANDIDATES) set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "${FLAG}") unset(OpenMP_FLAG_DETECTED CACHE) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Try OpenMP CXX flag = [${FLAG}]") endif() check_cxx_source_compiles("${OpenMP_CXX_TEST_SOURCE}" OpenMP_FLAG_DETECTED) set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") if(OpenMP_FLAG_DETECTED) set(OpenMP_CXX_FLAGS_INTERNAL "${FLAG}") break() endif() endforeach() set(OpenMP_CXX_FLAGS "${OpenMP_CXX_FLAGS_INTERNAL}" CACHE STRING "C++ compiler flags for OpenMP parallization") list(APPEND _OPENMP_REQUIRED_VARS OpenMP_CXX_FLAGS) unset(OpenMP_CXX_FLAG_CANDIDATES) unset(OpenMP_CXX_TEST_SOURCE) unset(OpenMP_C_TEST_SOURCE) if (NOT "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") set(OpenMP_CXX_LIBRARY "${OpenMP_CXX_FLAGS}" CACHE STRING "CXX library for OpenMP") list(APPEND _OPENMP_REQUIRED_VARS OpenMP_CXX_LIBRARY) endif() set(OpenMP_CXX_LIBRARIES "${OpenMP_CXX_LIBRARY}") if (NOT OpenMP_CXX_SPEC_DATE) _OPENMP_GET_SPEC_DATE("CXX" OpenMP_CXX_SPEC_DATE_INTERNAL) set(OpenMP_CXX_SPEC_DATE "${OpenMP_CXX_SPEC_DATE_INTERNAL}" CACHE INTERNAL "C++ compiler's OpenMP specification date") endif() unset(OpenMP_C_CXX_CHECK_VERSION_SOURCE) endif() # check Fortran compiler if(CMAKE_Fortran_COMPILER_LOADED) # if these are set then do not try to find them again, # by avoiding any try_compiles for the flags if(OpenMP_Fortran_FLAGS) unset(OpenMP_Fortran_FLAG_CANDIDATES) else() _OPENMP_FLAG_CANDIDATES("Fortran") include(CheckFortranSourceCompiles) endif() foreach(FLAG IN LISTS OpenMP_Fortran_FLAG_CANDIDATES) set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "${FLAG}") unset(OpenMP_FLAG_DETECTED CACHE) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Try OpenMP Fortran flag = [${FLAG}]") endif() check_fortran_source_compiles("${OpenMP_Fortran_TEST_SOURCE}" OpenMP_FLAG_DETECTED) set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") if(OpenMP_FLAG_DETECTED) set(OpenMP_Fortran_FLAGS_INTERNAL "${FLAG}") break() endif() endforeach() set(OpenMP_Fortran_FLAGS "${OpenMP_Fortran_FLAGS_INTERNAL}" CACHE STRING "Fortran compiler flags for OpenMP parallization") list(APPEND _OPENMP_REQUIRED_VARS OpenMP_Fortran_FLAGS) unset(OpenMP_Fortran_FLAG_CANDIDATES) unset(OpenMP_Fortran_TEST_SOURCE) set(OpenMP_Fortran_LIBRARY "${OpenMP_Fortran_FLAGS}" CACHE STRING "Fortran library for OpenMP") list(APPEND _OPENMP_REQUIRED_VARS OpenMP_Fortran_LIBRARY) set(OpenMP_Fortran_LIBRARIES "${OpenMP_Fortran_LIBRARY}") if (NOT OpenMP_Fortran_SPEC_DATE) _OPENMP_GET_SPEC_DATE("Fortran" OpenMP_Fortran_SPEC_DATE_INTERNAL) set(OpenMP_Fortran_SPEC_DATE "${OpenMP_Fortran_SPEC_DATE_INTERNAL}" CACHE INTERNAL "Fortran compiler's OpenMP specification date") endif() unset(OpenMP_Fortran_CHECK_VERSION_SOURCE) endif() set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) if(_OPENMP_REQUIRED_VARS) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenMP REQUIRED_VARS ${_OPENMP_REQUIRED_VARS}) mark_as_advanced(${_OPENMP_REQUIRED_VARS}) unset(_OPENMP_REQUIRED_VARS) else() message(SEND_ERROR "FindOpenMP requires C or CXX language to be enabled") endif() ================================================ FILE: cmake/SIFT3DPackage.cmake ================================================ ################################################################################ # SIFT3DPackage.cmake ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file to populate SIFT3D packages. ################################################################################ # Set the default package generator based on the OS if (WIN32) # NSIS for Windows set (_CPACK_GENERATOR "NSIS") elseif (APPLE) # Bundle for Mac set (_CPACK_GENERATOR "BUNDLE") elseif (UNIX) # Default to .tar.gz on Unix-like platforms set (_CPACK_GENERATOR "TGZ") # Override for specific Linux distributions if (CMAKE_SYSTEM_NAME MATCHES "Linux") # Try to read the Linux distribution if (EXISTS "/etc/issue") file (READ "/etc/issue" LINUX_ISSUE) # .deb for Ubuntu and Debian if (${LINUX_ISSUE} MATCHES "Ubuntu" OR ${LINUX_ISSUE} MATCHES "Debian") set (_CPACK_GENERATOR "DEB") # .rpm for Fedora and OpenSuSE elseif (${LINUX_ISSUE} MATCHES "Fedora" OR ${LINUX_ISSUE} MATCHES "SUSE") set (_CPACK_GENERATOR "RPM") endif () endif () endif () else () message (FATAL_ERROR "Unable to determine the default package generator for this operating system") endif () set (CPACK_GENERATOR ${_CPACK_GENERATOR} CACHE STRING "The package generation program") # Global CPack variables set (CPACK_PACKAGE_NAME "SIFT3D") set (CPACK_PACKAGE_CONTACT "Blaine Rister blaine@stanford.edu") set (CPACK_RESOURCE_FILE_LICENSE ${LICENSE_FILE}) set (CPACK_RESOURCE_FILE_README ${README_FILE}) set (CPACK_PACKAGE_VERSION ${SIFT3D_VERSION}) set (CPACK_PACKAGE_DESCRIPTION "Extracts keypoints and descriptors from 3D images. Also contians libraries for image processing and registration. Includes wrappers for Matlab.") # Use the CMake install path, unless this is Windows, in which case this would # break CPack if (NOT WIN32) set (CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) endif () # Generator-specific CPack variables if (CPACK_GENERATOR STREQUAL "NSIS") # Add the option to append SIFT3D to the path set (CPACK_NSIS_MODIFY_PATH ON) elseif (CPACK_GENERATOR STREQUAL "BUNDLE") message(FATAL_ERROR "Bundle support not yet implemented") # TODO Do we need a bundle info file? elseif (CPACK_GENERATOR STREQUAL "DEB") # Set the target platform in Debian terms if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") set (CPACK_DEBIAN_PACKAGE_ARCHITECTURE amd64) elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "i686") set (CPACK_DEBIAN_PACKAGE_ARCHITECTURE i386) else () set (CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) endif () message ("-- Configured Debian package for architecture ${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") # Set the standard Debian package dependencies set (CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, zlib1g, libnifti2, libdcmtk5, liblapack3") # Set compiler-specific Debian package dependencies if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set (CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libgcc1") endif () elseif (CPACK_GENERATOR STREQUAL "RPM") message(FATAL_ERROR "RPM support not yet implemented") # TODO Add RPM package dependencies (see debian dependencies) endif () # Finally include the CPack module include (CPack) ================================================ FILE: doc/INSTALL_LINUX.md ================================================ # SIFT3D Linux Installation Instructions Copyright (c) 2015-2018 Blaine Rister et al., see LICENSE for details. # Installing the dependencies This program requires the following external libraries: - [zlib](http://www.zlib.net/) - [LAPACK](http://www.netlib.org/lapack/) In addition, the following libraries add optional support for reading and writing DICOM and NIFTI files: - [DCMTK](http://dicom.offis.de/dcmtk.php.en) - [nifticlib](http://sourceforge.net/projects/niftilib/files/nifticlib/) The optional I/O support will be compiled if DMCTK and nifticlib are found on your system. Otherwise, the programs will run as normal, throwing an error if you try to use an unsupported file format. On Ubuntu 16.04, the following command will install all dependencies: sudo apt-get install zlib1g-dev liblapack-dev libdcmtk-dev libnifti-dev # Installing SIFT3D ## Installing from binaries You can install SIFT3D in one of two ways, from binaries or from source. The easiest way is to install from binaries. Simply visit our [releases](https://github.com/bbrister/SIFT3D/releases) page, download the appropriate installer for your system, and run it. ## Installing from source This program has been successfully compiled and executed on the following Linux platforms: - Ubuntu Linux 16.04, using GCC 5.4.0 and CMake 3.5.1. This program requires the following tools to compile: - [CMake](http://www.cmake.org) - A suitable C/C++ compiler, such as GCC or Clang/LLVM. On Ubuntu 16.04, the following command will install CMake and GCC: sudo apt-get install build-essential cmake The following commands will generate Makefiles and use them to compile the binaries in a subdirectory called "build": cd /path/to/SIFT3D # Go to the directory where you downloaded SIFT3D mkdir build # Make a directory to store the binaries cd build cmake .. # Create the Makefiles make # Compile the program If for some reason CMake cannot find the dependencies, you can specify the paths manually with the CMake GUI. Use the following command to install the files: sudo make install ### Packaging To create your own binary package, invoke CMake with the BUILD_PACKAGE variable set to ON. Then build the 'package' target using the Makefiles. For example, cmake .. -DBUILD_PACKAGE=ON make package ### Troubleshooting If you are using an older version of Ubuntu, then you will need to update [DCMTK](http://dicom.offis.de/dcmtk.php.en) to the latest version before compiling SIFT3D. ================================================ FILE: doc/INSTALL_MAC.md ================================================ # SIFT3D Mac Installation Instructions Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. # Installing the dependencies This program requires the following external libraries: - [zlib](http://www.zlib.net/) - [LAPACK](http://www.netlib.org/lapack/) In addition, the following libraries add optional support for reading and writing DICOM and NIFTI files: - [DCMTK](http://dicom.offis.de/dcmtk.php.en) - [nifticlib](http://sourceforge.net/projects/niftilib/files/nifticlib/) The required dependencies come pre-installed on Mac. The optional I/O support will be compiled if DMCTK and nifticlib are found on your system. Otherwise, the programs will run as normal, throwing an error if you try to use an unsupported file format. As of version 10.10.5, you can install the optional I/O dependencies with [Homebrew](http://brew.sh/). First install Homebrew, if you haven't already, then run the following command: brew install dcmtk niftilib # Installing SIFT3D ## Installing from binaries We do not currently have binaries for SIFT3D on Mac. You must install from source. ## Installing from source This program has been successfully compiled and executed on the following Mac platforms: - Mac OSX 10.10.5, using Clang 6.1.0, LLVM 3.6.0 and CMake 3.3.1 This program requires the following tools to compile: - [CMake](http://www.cmake.org) - A suitable C/C++ compiler. such as GCC or Clang/LLVM. Clang/LLVM comes pre-installed. Using [Homebrew](http://brew.sh), the following command will install CMake. brew install cmake The following commands will generate Makefiles and use them to compile the binaries in a subdirectory called "build": cd /path/to/SIFT3D # Go to the directory where you downloaded SIFT3D mkdir build # Make a directory to store the binaries cd build cmake .. # Create the Makefiles make # Compile the program *Note: If you are compiling without DICOM support, leave DCMTK_DIR blank. If you installed DCMTK to some place other than /usr/local, set DCMTK_DIR to that path in the above CMake command.* If CMake cannot find the dependencies, you can specify the paths manually with the CMake GUI. Use the following command to install the files: sudo make install ================================================ FILE: doc/INSTALL_WINDOWS.md ================================================ # SIFT3D Windows Installation Instructions Copyright (c) 2015-2018 Blaine Rister et al., see LICENSE for details. ## Installing from binaries You can install SIFT3D in one of two ways, from binaries or from source. The easiest way is to install from binaries. Simply visit our [releases](https://github.com/bbrister/SIFT3D/releases) page, download the appropriate installer for your system, and run it. ## Installing from source *This is a difficult process on Windows, recommended only for advanced users.* This program has been successfully compiled and executed on the following Windows platforms: * Windows 10 64-bit, using [TDM-GCC](http://tdm-gcc.tdragon.net/) 5.10 and CMake 3.3.1 * Windows 8 64-bit, using the same * Windows 7 64-bit, using the same In addition, the compiled C libraries have been linked to on the following platforms: * Windows 7 64-bit, using Visual Studio 2012 Please follow the instructions below to compile and install SIFT3D from source. 1. Install [CMake](http://www.cmake.org). 2. Install MinGW via [TDM-GCC](http://tdm-gcc.tdragon.net/) 1. Download the installer 2. Run the installer and remember to select the GCC packages C, C++, Fortran and OpenMP * OpenMP is for optional multithreading, the rest are mandatory 3. Use [gnumex](http://gnumex.sourceforge.net/documentation.html#L131) to hack Matlab to compile MEX files with MinGW. *This needed only for the Matlab toolbox. Non-Matlab users can skip this step.* 1. Download and extract gnumex 2. Run Matlab as an administrator 3. Run the "gnumex" program from within Matlab 4. Make sure the required paths are detected 5. Generate the files 6. Run "mex -setup" from within Matlab. If all goes well, GCC will show up as an option. Follow the prompt to set up MEX with GCC. 4. Install [LAPACK for Windows](http://icl.cs.utk.edu/lapack-for-windows/lapack/index.html#libraries). *(Note: Any other BLAS/LAPACK package should work as well. I have had success compiling* [OpenBlas](http://www.openblas.net/) *with MinGW and CMake.)* 1. Download the binaries for MinGW and your verison of Windows (lapack.dll, blas.dll) 2. Move lapack.dll and blas.dll to the TDM-GCC/bin directory 5. Install [zlib](http://zlib.net/). 1. Download and extract the most recent version 2. Use the CMake GUI to generate MinGW Makefiles 1. Set the source folder to the location of zlib 2. Generate -> MinGW Makefiles 3. Compile and install with MinGW 1. cd to the build directory 2. "mingw32-make" 3. "mingw32-make install" (may require administrator privileges) 6. Install [nifticlib](http://sourceforge.net/projects/niftilib/files/nifticlib/). *This step is needed only for NIFTI I/O. Users not needing to read and write NIFTI files can skip this step.* 1. Download and extract the newest version 2. Generate MinGW Makefiles with CMake 1. Set the source folder to the location of nifticlib 2. Generate -> MinGW Makefiles * You may have to set the variable ZLIB_LIBRARY to the directory where zlib was installed. 3. Compile and install with MinGW 1. cd to build directory 2. "mingw32-make" 3. "mingw32-make install" (may require administrator privileges) 7. Install [DCMTK](http://www.dcmtk.org). *This step is needed only for DICOM I/O. Users not needing to read and write DICOM files can skip this step.* 1. Download and extract the newest version. If there is a binary installer for your version of Windows, you can use that and skip the following sections. As of 10/06/2015, there is no binary for 64-bit Windows, so we compiled DCMTK from source. *Note: We could not compile DCMTK 3.6.0 on Windows with CMake and MinGW. Instead, we used a snapshot of version 3.6.1, retrieved on 09/24/2015.* 2. Generate MinGW Makefiles with CMake 1. Set the source folder to the location of DCMTK 2. Generate -> MinGW Makefiles 3. Compile and install with MinGW 1. cd to build directory 2. "mingw32-make" 3. "mingw32-make install" (may require administrator privileges) 8. Install SIFT3D. 1. Download this repository 2. Generate MinGW Makefiles with CMake 1. Navigate to SIFT3D 2. Select "MinGW Makefiles" 3. Configure 4. Configuration may fail if CMake cannot find the dependencies. If so, set the appropriate variables and configure again. 1. If configuration fails due to zlib, check 'advanced' and set all ZLIB_* variables to the appropriate paths. For example, set ZLIB_LIBRARY (or ZLIB_LIBRARY_RELEASE) to libzlib.dll and ZLIB_INCLUDE_DIR to the include directory of your zlib installation. 2. If configuration fails due to DCMTK, set DCMTK_DIR to the install location of DCMTK. 3. If configuration fails due to NIFTI, set NIFTI_DIR to the install location of nifticlib. 5. *Note: this step applies only to users compiling the optional Matlab toolbox.* Ensure that the Matlab libraries are .dll's and not .lib's. Manually edit the paths for Matlab_MEX_LIBRARY, Matlab_MX_LIBRARY, MWLAPACK_LIBRARY, and MWBLAS_LIBRARY, so that "libmex.lib" is changed to "libmex.dll", etc. This requires locating these files within your Matlab installation. Check the "bin" directories for .dll's. 6. Generate -> MinGW Makefiles 3. Compile and install with MinGW 1. cd to the build directory 2. "mingw32-make" 3. "mingw32-make install" (may require administrator privileges) ### Packaging To create your own binary package, set the BUILD_PACKAGE variable to ON in the CMake GUI. Then build the 'package' target using the Makefiles. For example, mingw32-make package ## Caveats This program was originally developed for Unix-like platforms, so some features have been disabled in the Windows version. * By default, the command line interface is not compiled on Windows systems. If you wish to compile it, you can set the CMake variable BUILD_CLI to ON. This is not officially supported, and the resulting executables may not function correctly. The good news is that you can still access SIFT3D through the C libraries and wrappers for other languages. * There are problems writing .nii.gz files in Windows. Note that all other image formats still work, including .nii without gzip compression. From the Matlab wrapper function imWrite3D, we work around this issue by first writing the .nii file, then compressing with a Matlab function call. However, you may experience issues when writing a .nii.gz file from the C library function im_write. ================================================ FILE: examples/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for example programs. ################################################################################ # The data directory set (DATA_DIR "data") # The name of the examples subdirectory, where compiled examples are found. On Windows, this is simply bin, as the executables must be in the same directory as the DLLs. On other platforms, this is a separate "examples" directory. if (WIN32) set (EXAMPLES_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) else () set (EXAMPLES_DIR "examples") set (EXAMPLES_PATH "${CMAKE_BINARY_DIR}/${EXAMPLES_DIR}") endif () # Copy the example data configure_file("${DATA_DIR}/1.nii.gz" "${EXAMPLES_PATH}/1.nii.gz" COPYONLY) configure_file("${DATA_DIR}/2.nii.gz" "${EXAMPLES_PATH}/2.nii.gz" COPYONLY) # The example programs add_executable (featuresC featuresC.c) target_link_libraries (featuresC PUBLIC sift3D imutil) add_executable (registerC registerC.c) target_link_libraries (registerC PUBLIC reg sift3D imutil) add_executable (ioC ioC.c) target_link_libraries (ioC PUBLIC imutil) # Send all files to the examples subdirectory set_target_properties(featuresC registerC ioC PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${EXAMPLES_PATH} LIBRARY_OUTPUT_DIRECTORY ${EXAMPLES_PATH} RUNTIME_OUTPUT_DIRECTORY ${EXAMPLES_PATH} ) ================================================ FILE: examples/featuresC.c ================================================ /* ----------------------------------------------------------------------------- * featuresC.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Example of extracting and visualizing SIFT3D keypoints and descriptors using * the C API. */ /* System headers */ #include /* SIFT3D headers */ #include "immacros.h" #include "imutil.h" #include "sift.h" /* Example file paths */ const char *im_path = "1.nii.gz"; const char *keys_path = "1_keys.csv.gz"; const char *desc_path = "1_desc.csv.gz"; const char *draw_path = "1_keys.nii.gz"; /* This illustrates how to use SIFT3D within a function, and free all memory * afterwards. */ int demo(void) { Image im, draw; Mat_rm keys; SIFT3D sift3d; Keypoint_store kp; SIFT3D_Descriptor_store desc; // Initialize the intermediates init_Keypoint_store(&kp); init_SIFT3D_Descriptor_store(&desc); init_im(&im); init_im(&draw); if (init_Mat_rm(&keys, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) return 1; if (init_SIFT3D(&sift3d)) { cleanup_Mat_rm(&keys); return 1; } // Read the image if (im_read(im_path, &im)) goto demo_quit; // Detect keypoints if (SIFT3D_detect_keypoints(&sift3d, &im, &kp)) goto demo_quit; // Write the keypoints to a file if (write_Keypoint_store(keys_path, &kp)) goto demo_quit; printf("Keypoints written to %s. \n", keys_path); // Extract descriptors if (SIFT3D_extract_descriptors(&sift3d, &kp, &desc)) goto demo_quit; // Write the descriptors to a file if (write_SIFT3D_Descriptor_store(desc_path, &desc)) goto demo_quit; printf("Descriptors written to %s. \n", desc_path); // Convert the keypoints to a matrix if (Keypoint_store_to_Mat_rm(&kp, &keys)) goto demo_quit; // Draw the keypoints if (draw_points(&keys, SIFT3D_IM_GET_DIMS(&im), 1, &draw)) goto demo_quit; // Write the drawn keypoints to a file if (im_write(draw_path, &draw)) goto demo_quit; printf("Keypoints drawn in %s. \n", draw_path); // Clean up im_free(&im); im_free(&draw); cleanup_Mat_rm(&keys); cleanup_SIFT3D(&sift3d); cleanup_Keypoint_store(&kp); cleanup_SIFT3D_Descriptor_store(&desc); return 0; demo_quit: // Clean up and return an error im_free(&im); im_free(&draw); cleanup_Mat_rm(&keys); cleanup_SIFT3D(&sift3d); cleanup_Keypoint_store(&kp); cleanup_SIFT3D_Descriptor_store(&desc); return 1; } int main(void) { int ret; // Do the demo ret = demo(); // Check for errors if (ret != 0) { fprintf(stderr, "Fatal demo error, code %d. \n", ret); return 1; } return 0; } ================================================ FILE: examples/featuresMatlab.m ================================================ % featuresMatlab.m % % This script shows how to extract SIFT3D features from a volumetric image. % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Load the image [im, units] = imRead3D('data/1.nii.gz'); % Detect keypoints keys = detectSift3D(im, 'units', units); % Extract descriptors [desc, coords] = extractSift3D(keys); % Clear MEX memory clear mex ================================================ FILE: examples/ioC.c ================================================ /* ----------------------------------------------------------------------------- * ioC.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Short example of file I/O, converting a NIFTI file to DICOM. */ /* System headers */ #include /* SIFT3D headers */ #include "imutil.h" /* Example file paths */ const char in_path[] = "1.nii.gz"; const char out_path[] = "1dicom"; /* This illustrates how to use images within a function, and free all memory * afterwards. */ int demo(void) { Image im; // You must call this before any Image struct can be used init_im(&im); // Read the NIFTI image as input if (im_read(in_path, &im)) goto demo_quit; // Write it to a DICOM series if (im_write(out_path, &im)) goto demo_quit; // Clean up im_free(&im); return 0; demo_quit: // Clean up and return an error im_free(&im); return 1; } int main(void) { int ret; // Do the demo ret = demo(); // Check for errors if (ret != 0) { fprintf(stderr, "Fatal demo error, code %d. \n", ret); return 1; } return 0; } ================================================ FILE: examples/ioMatlab.m ================================================ % ioMatlab % % Example of using file IO functions with SIFT3D % % Copyright (c) 2015 Blaine Rister et al., see LICENSE for details. % Load the from a NIFTI file imNifti = imRead3D('data/1.nii.gz'); % Save it as a multi-slice DICOM file imWrite3D('1.dcm', imNifti); ================================================ FILE: examples/manualFeaturesMatlab.m ================================================ % manualFeaturesMatlab.m % % This script shows how to manually define keypoints in a volumetric image, % and extract SIFT3D feature descriptors at those locations. % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Load the image [im, units] = imRead3D('data/1.nii.gz'); % Define a keypoint keys = keypoint3D([100 100 20]); % Assign its orientation [keys, conf] = orientation3D(keys, im, units); % Extract a descriptor [desc, coords] = extractSift3D(keys, im, units); ================================================ FILE: examples/registerC.c ================================================ /* ----------------------------------------------------------------------------- * registerC.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Example of registering two images using the C API. */ /* System headers */ #include /* SIFT3D headers */ #include "imutil.h" #include "sift.h" #include "reg.h" /* Example file paths */ const char *ref_path = "1.nii.gz"; const char *src_path = "2.nii.gz"; const char *match_path = "1_2_matches.nii.gz"; const char *warped_path = "2_warped.nii.gz"; const char *affine_path = "1_2_affine.csv.gz"; /* This illustrates how to use Reg_SIFT3D within a function, freeing all memory * afterwards. */ int demo(void) { Image src, ref, warped; Reg_SIFT3D reg; Affine affine; // Initialize the intermediates init_im(&src); init_im(&ref); init_im(&warped); if (init_tform(&affine, AFFINE)) return 1; if (init_Reg_SIFT3D(®)) { cleanup_tform(&affine); return 1; } // Read the images if (im_read(src_path, &src) || im_read(ref_path, &ref)) goto demo_quit; // Set the images if (set_src_Reg_SIFT3D(®, &src) || set_ref_Reg_SIFT3D(®, &ref)) goto demo_quit; // Match features and solve for an affine transformation if (register_SIFT3D(®, &affine)) goto demo_quit; // Write the transformation to a file if (write_tform(affine_path, &affine)) goto demo_quit; // Warp the source image if (im_inv_transform(&affine, &src, LINEAR, SIFT3D_TRUE, &warped)) goto demo_quit; // Write the warped image to a file if (im_write(warped_path, &warped)) goto demo_quit; // Clean up im_free(&src); im_free(&ref); im_free(&warped); cleanup_Reg_SIFT3D(®); cleanup_tform(&affine); return 0; demo_quit: // Clean up and return an error im_free(&src); im_free(&ref); im_free(&warped); cleanup_Reg_SIFT3D(®); cleanup_tform(&affine); return 1; } int main(void) { int ret; // Do the demo ret = demo(); // Check for errors if (ret != 0) { fprintf(stderr, "Fatal demo error, code %d. \n", ret); return 1; } return 0; } ================================================ FILE: examples/registerMatlab.m ================================================ % registerMatlab % % Example of image registration in Matlab % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Load the images [src, srcUnits] = imRead3D('data/1.nii.gz'); [ref, refUnits] = imRead3D('data/2.nii.gz'); % Register [A, matchSrc, matchRef] = registerSift3D(src, ref, 'srcUnits', ... srcUnits, 'refUnits', refUnits); % Clear MEX memory clear mex ================================================ FILE: imutil/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for the image processing utility library. ################################################################################ # Find BLAS find_package (BLAS QUIET) if (NOT BLAS_FOUND) message (FATAL_ERROR "BLAS not found. Please set the variable " "BLAS_LIBRARIES to the location of the BLAS library on " "your system.") endif() # Find LAPACK find_package (LAPACK QUIET) if (NOT LAPACK_FOUND) message (FATAL_ERROR "LAPACK not found. Please set the variable " "LAPACK_LIBRARIES to the location of the LAPACK library on your " "system.") endif () # Find DCMTK find_package (DCMTK QUIET) if (DCMTK_FOUND) # Add platform-specific DCMTK dependencies if (WIN32) # Add ws2_32 list (APPEND DCMTK_LIBRARIES "ws2_32") endif () # Get the base DCMTK include dir. Note that DCMTK_DIR is set incorrectly on # Linux, so we must add additional paths find_path (DCMTK_BASE_INCLUDE_PARENT_DIR "include/dcmtk" PATHS ${DCMTK_DIR} "${DCMTK_config_INCLUDE_DIR}/../../..") set (DCMTK_BASE_INCLUDE_DIR "${DCMTK_BASE_INCLUDE_PARENT_DIR}/include" CACHE PATH "DCMTK include directory") if (_DCMTK_BASE_INCLUDE_PARENT_DIR STREQUAL "DCMTK_BASE_INCLUDE_PARENT_DIR-NOTFOUND") message (FATAL_ERROR "Failed to find the DCMTK include " "directory. Please set the variable " "DCMTK_BASE_INCLUDE_DIR to /include, " "or disable DICOM support by setting WITH_DICOM to " "false.") endif () # Add the base dir to the DCMTK include paths list(APPEND DCMTK_INCLUDE_DIRS ${DCMTK_BASE_INCLUDE_DIR}) # Check if there is a configuration file for DCMTK find_file(DCMTK_CONFIG_FILE NAMES "cfunix.h" "cfwin32.h" PATHS ${DCMTK_config_INCLUDE_DIR} NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH) if (DCMTK_CONFIG_FILE STREQUAL "DCMTK_CONFIG_FILE-NOTFOUND") set (DCMTK_HAVE_CONFIG_FILE false) else () set (DCMTK_HAVE_CONFIG_FILE true) endif () message (STATUS "Found DCMTK.") elseif (WITH_DICOM) message (FATAL_ERROR "DCMTK not found. Please set the variable " "DCMTK_DIR to the location of DCMTK on your system, or disable" "DICOM support by setting WITH_DICOM to false.") else () message (STATUS "DCMTK not found. Compiling without DICOM support. " "To enable DICOM support, set the variable DCMTK_DIR to the " "location of DCMTK on your system.") endif () set (WITH_DICOM ${DCMTK_FOUND} CACHE BOOL "Compile DICOM I/O support") if (WITH_DICOM) message (STATUS "Building with DICOM support.") endif () # Optionally find NIFTI find_package (NIFTI QUIET) if (NIFTI_FOUND) message (STATUS "Found NIFTI.") elseif (WITH_NIFTI) message (FATAL_ERROR "Failed to find nifticlib. Please set the " "variable NIFTI_DIR to the location of DCMTK on your system, " "or disable NIFTI support by setting WITH_NIFTI to false.") else() message (STATUS "Failed to find nifticlib. Compiling without NIFTI " "support. To enable NIFTI support, set the variable NIFTI_DIR " "to the location of NIFTI on your system.") endif() set (WITH_NIFTI ${NIFTI_FOUND} CACHE BOOL "Compile NIFTI I/O support") if (WITH_NIFTI) message (STATUS "Building with NIFTI support.") endif () # Find iconv on Mac if (APPLE) find_library (ICONV_LIBRARY NAMES iconv libiconv libiconv-2 c REQUIRED) endif () # Find MinGW dependencies if (MINGW) find_package (MINGW REQUIRED) endif () # Macro to optionally add DCMTK to a target macro (add_DICOM arg) if (WITH_DICOM) target_include_directories(${arg} PRIVATE ${DCMTK_INCLUDE_DIRS}) target_link_libraries (${arg} PRIVATE ${DCMTK_LIBRARIES}) target_compile_definitions(${arg} PRIVATE "SIFT3D_WITH_DICOM") if (DCMTK_HAVE_CONFIG_FILE) target_compile_definitions (${arg} PRIVATE "HAVE_CONFIG_H") endif () endif () endmacro () # Macro to optionally add NIFTI to a target macro (add_NIFTI arg) if (WITH_NIFTI) target_compile_definitions(${arg} PRIVATE "SIFT3D_WITH_NIFTI") target_include_directories(${arg} PRIVATE ${NIFTI_INCLUDE_DIRS}) target_link_libraries (${arg} PRIVATE ${NIFTI_LIBRARIES}) endif () endmacro () # Format the compiler definitions set (IMUTIL_DEFINITIONS "SIFT3D_VERSION_NUMBER=${SIFT3D_VERSION}") # Check if the compiler uses strndup and strnlen check_function_exists (strnlen HAVE_STRNLEN) if (HAVE_STRNLEN) list (APPEND IMUTIL_DEFINITIONS "SIFT3D_HAVE_STRNLEN") endif () check_function_exists (strndup HAVE_STRNDUP) if (HAVE_STRNDUP) list (APPEND IMUTIL_DEFINITIONS "SIFT3D_HAVE_STRNDUP") endif () # Compile imutil add_library (imutil SHARED imutil.c nifti.c dicom.cpp) target_include_directories (imutil PUBLIC $ $ ) target_link_libraries (imutil PRIVATE ${LAPACK_LIBRARIES}) target_compile_definitions (imutil PRIVATE ${IMUTIL_DEFINITIONS}) add_DICOM(imutil) add_NIFTI(imutil) install (FILES imtypes.h immacros.h imutil.h kernels.cl DESTINATION ${INSTALL_INCLUDE_DIR}) # Link to system libraries target_link_libraries(imutil PRIVATE ${ZLIB_LIBRARIES} ${M_LIBRARY}) target_include_directories(imutil PRIVATE ${ZLIB_INCLUDE_DIR}) if (APPLE) target_link_libraries(imutil PUBLIC ${ICONV_LIBRARY}) endif () # Configure the installation install (TARGETS imutil EXPORT SIFT3D-targets RUNTIME DESTINATION ${INSTALL_BIN_DIR} LIBRARY DESTINATION ${INSTALL_LIB_DIR} ARCHIVE DESTINATION ${INSTALL_LIB_DIR} ) # OS-specific installation if (WIN32) # Make a list of all external dependencies set (DEPS ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES} ${ZLIB_LIBRARIES} ${OpenMP_C_LIBRARIES} ${MINGW_LIBRARIES}) if (WITH_DICOM) list (APPEND DEPS ${DCMTK_LIBRARIES}) endif () if (WITH_NIFTI) list (APPEND DEPS ${NIFTI_LIBRARIES}) endif () function (get_runtime_deps ARG_DEPS ARG_RUNTIME_DEPS) # Process each dependency, adding a runtime dependency if # necessary set (${ARG_RUNTIME_DEPS} "") foreach (DEP IN LISTS ${ARG_DEPS}) # Get the file extension get_filename_component (DEP_EXT ${DEP} EXT) # Process shared libraries if (DEP_EXT STREQUAL ".dll") list (APPEND ${ARG_RUNTIME_DEPS} ${DEP}) # Process MinGW import libraries elseif (DEP_EXT STREQUAL ".dll.a") # Extract the filename, parent and grandparent directories get_filename_component (DEP_NAME ${DEP} NAME) get_filename_component (DEP_DIR ${DEP} DIRECTORY) get_filename_component (DEP_DIR_DIR ${DEP_DIR} DIRECTORY) # Get the name of the .dll version string (REGEX REPLACE ".dll.a$" ".dll" DEP_DLL_NAME ${DEP_NAME}) # Find the corresponding .dll string (REGEX REPLACE ".dll" "_DLL" DEP_DLL_VAR ${DEP_DLL_NAME}) find_file (${DEP_DLL_VAR} ${DEP_DLL_NAME} PATHS ${DEP_DIR} ${DEP_DIR_DIR} PATH_SUFFIXES "bin" "lib") if (${DEP_DLL_VAR} STREQUAL "${DEP_DLL_NAME}-NOTFOUND") message (FATAL_ERROR "Failed to find runtime dependency ${DEP_DLL_NAME}") endif () # The .dll, not the .dll.a, becomes a runtime dependency list (APPEND ${ARG_RUNTIME_DEPS} ${${DEP_DLL_VAR}}) endif () endforeach () # Set the return value set (${ARG_RUNTIME_DEPS} ${${ARG_RUNTIME_DEPS}} PARENT_SCOPE) endfunction () # Convert dependencies to runtime dependencies get_runtime_deps (DEPS RUNTIME_DEPS) # Copy the runtime dependencies to the Windows DLL file (COPY ${RUNTIME_DEPS} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) # Add the runtime dependencies to the Windows installer install (FILES ${RUNTIME_DEPS} DESTINATION ${INSTALL_BIN_DIR}) endif () # If Matlab was found, compile a copy for use with Matlab libraries if (BUILD_Matlab) add_library (meximutil SHARED imutil.c nifti.c dicom.cpp) target_compile_definitions (meximutil PUBLIC "SIFT3D_MEX") target_compile_definitions (meximutil PRIVATE ${IMUTIL_DEFINITIONS}) target_include_directories (meximutil PUBLIC $ $ ) target_include_directories(meximutil PUBLIC ${Matlab_INCLUDE_DIRS}) target_include_directories (meximutil PRIVATE ${ZLIB_INCLUDE_DIR}) target_link_libraries (meximutil PUBLIC ${Matlab_LIBRARIES}) target_link_libraries (meximutil PRIVATE ${Matlab_MWLAPACK_LIBRARY} ${Matlab_MWBLAS_LIBRARY} ${ZLIB_LIBRARIES} ${M_LIBRARY}) add_DICOM(meximutil) add_NIFTI(meximutil) set_target_properties (meximutil PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} LIBRARY_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} RUNTIME_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} ) install (TARGETS meximutil RUNTIME DESTINATION ${INSTALL_TOOLBOX_DIR} LIBRARY DESTINATION ${INSTALL_TOOLBOX_DIR} ARCHIVE DESTINATION ${INSTALL_TOOLBOX_DIR} ) if (WIN32) # The toolbox has the same dependencies except for BLAS and LAPACK set (TOOLBOX_DEPS ${DEPS}) list (REMOVE_ITEM TOOLBOX_DEPS ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES} ) get_runtime_deps (TOOLBOX_DEPS TOOLBOX_RUNTIME_DEPS) file (COPY ${TOOLBOX_RUNTIME_DEPS} DESTINATION ${BUILD_TOOLBOX_DIR}) install (FILES ${TOOLBOX_RUNTIME_DEPS} DESTINATION ${INSTALL_TOOLBOX_DIR}) endif () endif () # Add the code snippets add_subdirectory (templates) ================================================ FILE: imutil/dicom.cpp ================================================ /* ----------------------------------------------------------------------------- * dicom.cpp * ----------------------------------------------------------------------------- * Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * C-language wrapper for the DCMTK library. * ----------------------------------------------------------------------------- */ /* SIFT3D includes */ #include "imutil.h" #include "immacros.h" #include "dicom.h" #ifndef SIFT3D_WITH_DICOM /* Return error messages if this was not compiled with DICOM support. */ static int dcm_error_message() { SIFT3D_ERR("dcm_error_message: SIFT3D was not compiled with DICOM " "support!\n"); return SIFT3D_WRAPPER_NOT_COMPILED; } int read_dcm(const char *path, Image *const im) { return dcm_error_message(); } int read_dcm_dir(const char *path, Image *const im) { return dcm_error_message(); } int write_dcm(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val) { return dcm_error_message(); } int write_dcm_dir(const char *path, const Image *const im, const Dcm_meta *const meta) { return dcm_error_message(); } #else /*----------------Include the very picky DCMTK----------------*/ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #define INCLUDE_CSTDIO #define INCLUDE_CSTRING #include "dcmtk/ofstd/ofstdinc.h" #ifdef HAVE_GUSI_H #include #endif #include "dcmtk/dcmdata/dctk.h" /* for various dcmdata headers */ #include "dcmtk/dcmdata/cmdlnarg.h" /* for prepareCmdLineArgs */ #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */ #include "dcmtk/ofstd/ofconapp.h" /* for OFConsoleApplication */ #include "dcmtk/ofstd/ofcmdln.h" /* for OFCommandLine */ #include "dcmtk/oflog/oflog.h" /* for OFLogger */ #include "dcmtk/dcmimgle/dcmimage.h" /* for DicomImage */ #include "dcmtk/dcmimgle/diutils.h" /* for DIPixel */ #include "dcmtk/dcmimage/diregist.h" /* include to support color images */ #include "dcmtk/dcmdata/dcrledrg.h" /* for DcmRLEDecoderRegistration */ #include "dcmtk/dcmjpeg/djdecode.h" /* for dcmjpeg decoders */ #include "dcmtk/dcmjpeg/dipijpeg.h" /* for dcmimage JPEG plugin */ #include "dcmtk/dcmjpeg/djencode.h" /* for JPEG encoding */ #include "dcmtk/dcmjpeg/djrplol.h" /* for DJ_RPLossless */ #include "dcmtk/dcmseg/segment.h" /* Dicom segmentations */ #ifdef WITH_ZLIB #include /* for zlibVersion() */ #endif /*---------------------------------------------------------*/ /* Other includes */ #include #include #include #include #include #include #include #include /* Macro to call a C++ function and catch any exceptions it throws, * returning SIFT3D_FAILURE when an exception is caught. The return value is * stored in ret. */ #define CATCH_EXCEPTIONS(ret, tag, fun, ...) \ try { \ ret = (fun)( __VA_ARGS__ ); \ } catch (std::exception &e) { \ SIFT3D_ERR("%s: %s\n", tag, e.what()); \ ret = SIFT3D_FAILURE; \ } catch (...) { \ SIFT3D_ERR("%s: unexpected exception \n", tag); \ ret = SIFT3D_FAILURE; \ } \ /* Format strings for DICOM tags */ static const char *pixelSpacingFmt = "%lf\\%lf"; // Pixel Spacing static const char *imPosPatientFmt = "%f\\%f\\%f"; // ImagePositionPatient static const char *imOriPatientFmt = "%f\\%f\\%f\\%f\\%f\\%f"; // ImageOrientationPatient /* File separator in string form */ const std::string sepStr(1, SIFT3D_FILE_SEP); /* Dicom parameters */ static const unsigned int dcm_bit_width = 8; // Bits per pixel /* DICOM metadata defaults */ static const char *default_patient_name = "DefaultSIFT3DPatient"; static const char *default_series_descrip = "Series generated by SIFT3D"; static const char *default_patient_id = "DefaultSIFT3DPatientID"; static const char default_instance_num = 1; /* Types of DICOMs which necessitate special processing */ enum image_type { DSO, PET, OTHER }; /* Helper declarations */ class Dicom; static bool isLittleEndian(void); static void default_Dcm_meta(Dcm_meta *const meta); static int load_file(const char *path, DcmFileFormat &fileFormat); static int read_dcm_cpp(const char *path, Image *const im); static int read_dcm_img(const Dicom &dicom, Image *const im); static int read_dso(const char *imDir, Dicom &dso, Image *const mask); static int cvec_max_abs(const Cvec *v, float *const val, int *const pos); static int read_dcm_dir_meta(const char *path, std::vector &dicoms); static int read_dcm_dir_cpp(const char *path, Image *const im); static int write_dcm_cpp(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val); static int write_dcm_dir_cpp(const char *path, const Image *const im, const Dcm_meta *const meta); static void set_meta_defaults(const Dcm_meta *const meta, Dcm_meta *const meta_new); static int dcm_resize_im(const std::vector &dicoms, Image *const im); static int write_subvolume(const Dicom &dicom, Image *const main, const int mainOffset, Image *const sub, const int start, const int end); /* Helper class to store DICOM data. */ class Dicom { private: int axes[2]; // Data axes, e.g. {0, 1} means x, y int axisSigns[2]; // Axis signs, negative means go backwards std::string filename; // DICOM file name std::string classUID; std::string seriesUID; // Series UID std::string instanceUID; // Instance UID double sortCoord; // Coordinate by which the series is sorted. Usually z double ux, uy, uz; // Voxel spacing in real-world coordinates int nx, ny, nz, nc; // Image dimensions int sortAxis; // The axis corresponding to sortCoord bool valid; // Data validity public: /* Data is initially invalid */ Dicom() : valid(false) {}; ~Dicom() {}; /* Load a file */ Dicom(const char *filename, const int defaults3D=0); /* Get the multiplier to convert a PET image to SUV */ double PET_SUV_multiplier(void) const; /* Get a dimension by index */ int getDim(const int idx) const { switch (idx) { case 0: return getNx(); case 1: return getNy(); case 2: return getNz(); } return -1; } /* Get a unit by index */ int getUnit(const int idx) const { switch (idx) { case 0: return getUx(); case 1: return getUy(); case 2: return getUz(); } return -1; } /* Get the image axis by index */ int getAxes(const int idx) const { return idx < 0 || idx > 2 ? -1 : axes[idx]; } /* Get the axis sign by index */ int getAxisSign(const int idx) const { return idx < 0 || idx > 2 ? -1 : axisSigns[idx]; } /* Get the x-dimension */ int getNx(void) const { return nx; } /* Get the y-dimension */ int getNy(void) const { return ny; } /* Get the z-dimension */ int getNz(void) const { return nz; } /* Get the number of channels */ int getNc(void) const { return nc; } /* Get the sorting axis */ int getSortAxis(void) const { return sortAxis; } /* Get the sorting coordinate (usually z) */ double getSortCoord(void) const { return sortCoord; } /* Get the units in the sorting dimension */ int getSortUnit(void) const { return getUnit(getSortAxis()); } /* Get the x-spacing */ double getUx(void) const { return ux; } /* Get the y-spacing */ double getUy(void) const { return uy; } /* Get the z-spacing */ double getUz(void) const { return uz; } /* Check whether or not the data is valid */ bool isValid(void) const { return valid; } /* Get the file name */ const char *name(void) const { return filename.c_str(); } /* Sort by z position */ bool operator < (const Dicom &dicom) const { return getSortCoord() < dicom.getSortCoord(); } /* Check if another DICOM file is from the same series */ bool eqSeries(const Dicom &dicom) const { return !seriesUID.compare(dicom.seriesUID); } /* Check for a matching SOPInstanceUID */ bool eqInstance(const char *uid) const { return !instanceUID.compare(uid); } /* Return an image type, based on the classUID */ enum image_type getType() const { if (!classUID.compare(UID_SegmentationStorage)) { return DSO; } else if ( !classUID.compare( UID_PositronEmissionTomographyImageStorage) || !classUID.compare( UID_LegacyConvertedEnhancedPETImageStorage) || !classUID.compare(UID_EnhancedPETImageStorage)) { return PET; } else { return OTHER; } } }; /* Read a DICOM file with DCMTK. */ static int load_file(const char *path, DcmFileFormat &fileFormat) { // Load the image as a DcmFileFormat OFCondition status = fileFormat.loadFile(path); if (status.bad()) { SIFT3D_ERR("load_file: failed to read DICOM file %s (%s)\n", path, status.text()); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Get the single maximum component from a Cvec, by absolue value. Returns the * element and its position. Returns SIFT3D_SUCCESS if the maximum is unique, * SIFT3D_FAILURE otherwise. */ static int cvec_max_abs(const Cvec *v, float *const val, int *const pos) { int k; const float vals[] = {v->x, v->y, v->z}; // Find the maximum float maxDiff = 0.f; float maxAbsVal = -1; for (k = 0; k < 3; k++) { const float thisVal = vals[k]; const float thisAbsVal = fabsf(thisVal); // Check for maximum. If so, write into val and pos if (thisAbsVal < maxAbsVal) continue; maxDiff = thisAbsVal - maxAbsVal; maxAbsVal = thisAbsVal; *val = thisVal; *pos = k; } // Test for unique maxima return maxDiff > 1e-2 ? SIFT3D_SUCCESS : SIFT3D_FAILURE; } /* Load the data from a DICOM file. If defaults3D is true, substitute default * values for 3D positioning information. Use this to load a single file even * if key metadata is missing, e.g. for 2D images. */ Dicom::Dicom(const char *path, const int defaults3D) : filename(path), valid(false) { // Read the file DcmFileFormat fileFormat; if (load_file(path, fileFormat)) return; DcmDataset *const data = fileFormat.getDataset(); // Get the SOPClass UID const char *classUIDStr; OFCondition status = data->findAndGetString(DCM_SOPClassUID, classUIDStr); if (status.bad() || classUIDStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get SOPClassUID " "from file %s (%s)\n", path, status.text()); return; } classUID = std::string(classUIDStr); // Get the series UID const char *seriesUIDStr; status = data->findAndGetString(DCM_SeriesInstanceUID, seriesUIDStr); if (status.bad() || seriesUIDStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get SeriesInstanceUID " "from file %s (%s)\n", path, status.text()); return; } seriesUID = std::string(seriesUIDStr); // Get the instance UID const char *instanceUIDStr; status = data->findAndGetString(DCM_SOPInstanceUID, instanceUIDStr); if (status.bad() || instanceUIDStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get SOPInstanceUID " "from file %s (%s)\n", path, status.text()); return; } instanceUID = std::string(instanceUIDStr); // Get the z coordinate if (getType() == DSO) { // DSOs don't always have coordinates sortCoord = -1; // DSOs don't always have units ux = uy = uz = -1.0; } else { #if 0 // Read the patient position const char *patientPosStr; status = data->findAndGetString(DCM_PatientPosition, patientPosStr); if (status.bad() || patientPosStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get " "PatientPosition from file %s (%s)\n", path, status.text()); return; } // Interpret the patient position to give the sign of the z axis double zSign; switch (patientPosStr[0]) { case 'H': zSign = -1.0; break; case 'F': zSign = 1.0; break; default: SIFT3D_ERR("Dicom.Dicom: unrecognized patient " "position: %d\n", patientPosStr); return; } #else //TODO: Is this needed? const double zSign = 1.0; #endif // Read the image position patient vector const char *imPosPatientStr; status = data->findAndGetString(DCM_ImagePositionPatient, imPosPatientStr); if (status.bad() || imPosPatientStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get " "ImagePositionPatient from file %s (%s)\n, defaulting" " to zeros. \n", path, status.text()); if (defaults3D) { imPosPatientStr = "0\\0\\0"; } else return; } // Parse the image position patient vector Cvec imPos; if (sscanf(imPosPatientStr, imPosPatientFmt, &imPos.x, &imPos.y, &imPos.z) != 3) { SIFT3D_ERR("Dicom.Dicom: failed to parse " "ImagePositionPatient value %s from file %s\n", imPosPatientStr, path); if (defaults3D) { imPos = {0, 0, 0}; } else return; } // Read the image orientation patient vector const char *imOriPatientStr; status = data->findAndGetString(DCM_ImageOrientationPatient, imOriPatientStr); if (status.bad() || imOriPatientStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get " "ImageOrientationPatient from file %s (%s)\n", path, status.text()); return; } // Parse the image orientation patient vectors Cvec imOri1, imOri2; if (sscanf(imOriPatientStr, imOriPatientFmt, &imOri1.x, &imOri1.y, &imOri1.z, &imOri2.x, &imOri2.y, &imOri2.z) != 6) { SIFT3D_ERR("Dicom.Dicom: failed to parse " "ImageOrientationPatient value %s from file %s\n", imOriPatientStr, path); return; } // Take the cross-product of orientation vectors to get the // image normal Cvec normal; SIFT3D_CVEC_CROSS(&imOri1, &imOri2, &normal); // Project the patient position on the image normal, to get the // sorting coordinate sortCoord = SIFT3D_CVEC_DOT(&imPos, &normal); // Get the image axes float oriVals[2]; if (cvec_max_abs(&imOri1, oriVals, axes) || cvec_max_abs(&imOri2, oriVals + 1, axes + 1)) { SIFT3D_ERR("Dicom.Dicom: unsupported format for " "imageOrientationPatient %s from file %s\n", imOriPatientStr, path); return; } // Get the sorting axis for (int k = 0; k < 3; k++) { if (axes[0] == k || axes[1] == k) continue; sortAxis = k; break; } // Compute the signs of the axes for (int k = 0; k < 2; k++) { axisSigns[k] = oriVals[k] >= 0 ? 1 : -1; } // Read the pixel spacing double spacing[2]; const char *pixelSpacingStr; status = data->findAndGetString(DCM_PixelSpacing, pixelSpacingStr); if (status.bad()) { SIFT3D_ERR("Dicom.Dicom: failed to get pixel spacing " "from file %s (%s)\n", path, status.text()); return; } if (sscanf(pixelSpacingStr, pixelSpacingFmt, spacing, spacing + 1) != 2) { SIFT3D_ERR("Dicom.Dicom: unable to parse pixel " "spacing from file %s \n", path); return; } if (spacing[0] <= 0.0 || spacing[1] <= 0.0) { SIFT3D_ERR("Dicom.Dicom: file %s has invalid pixel " "spacing [%f, %f]\n", path, ux, uy); return; } // Convert the pixel spacing into units based on the image axes double *units = &ux; for (int k = 0; k < 2; k++) { units[axes[k]] = spacing[k]; } // Read the slice thickness Float64 sliceThickness; status = data->findAndGetFloat64(DCM_SliceThickness, sliceThickness); if (!status.good()) { SIFT3D_ERR("Dicom.Dicom: failed to get slice " "thickness from file %s (%s)\n", path, status.text()); return; } if (sliceThickness <= 0.0) { SIFT3D_ERR("Dicom.Dicom: file %s has invalid slice " "thickness: %f \n", path, uz); return; } // Use the slice thickness as the units for the perpendicular // axis units[sortAxis] = sliceThickness; } // Load the DicomImage object DicomImage dicomImage(path); if (dicomImage.getStatus() != EIS_Normal) { SIFT3D_ERR("Dicom.Dicom: failed to open image %s (%s)\n", path, DicomImage::getString(dicomImage.getStatus())); return; } // Check for color images if (!dicomImage.isMonochrome()) { SIFT3D_ERR("Dicom.Dicom: reading of color DICOM images is " "not supported at this time \n"); return; } nc = 1; // Read the dimensions int *dims = &nx; dims[axes[0]] = dicomImage.getWidth(); dims[axes[1]] = dicomImage.getHeight(); dims[sortAxis] = dicomImage.getFrameCount(); if (nx < 1 || ny < 1 || nz < 1) { SIFT3D_ERR("Dicom.Dicom: invalid dimensions for file %s " "(%d, %d, %d)\n", path, nx, ny, nz); return; } // Set the window dicomImage.setMinMaxWindow(); valid = true; } /* Check the endianness of the machine. Returns true if the machine is little- * endian, false otherwise. */ static bool isLittleEndian(void) { volatile uint16_t i = 0x0123; return ((uint8_t *) &i)[0] == 0x23; } /* Set a Dcm_meta struct to default values. Generates new UIDs. */ static void default_Dcm_meta(Dcm_meta *const meta) { meta->patient_name = default_patient_name; meta->patient_id = default_patient_id; meta->series_descrip = default_series_descrip; dcmGenerateUniqueIdentifier(meta->study_uid, SITE_STUDY_UID_ROOT); dcmGenerateUniqueIdentifier(meta->series_uid, SITE_SERIES_UID_ROOT); dcmGenerateUniqueIdentifier(meta->instance_uid, SITE_INSTANCE_UID_ROOT); meta->instance_num = default_instance_num; } /* Parse a time (TM) string from a Dicom file. */ int parseTM(const char *const tm, double *const time) { // Buffers to hold the sub-strings char hh[3]; char mm[3]; // Subdivide into the TM into hours, minutes and seconds memcpy(hh, tm, 2); memcpy(mm, tm + 2, 2); hh[2] = mm[2] = '\0'; const char *const ss = tm + 4; // Parse the strings errno = 0; const int hours = atoi(hh); const int minutes = atoi(mm); const double seconds = strtod(ss, NULL); if (errno) return SIFT3D_FAILURE; // Add up the totals *time = hours * 60.0 * 60.0 + minutes * 60.0 + seconds; return SIFT3D_SUCCESS; } /* Get the multiplier to convert a PET scan to SUV values. Returns negative if * an error has occured. */ double Dicom::PET_SUV_multiplier(void) const { // Read the file const char *path = filename.c_str(); DcmFileFormat fileFormat; if (load_file(path, fileFormat)) return -1.0; DcmDataset *const data = fileFormat.getDataset(); // Read the body weight (0010, 1010) Float64 weight; OFCondition status = data->findAndGetFloat64(DCM_PatientWeight, weight); if (status.bad()) { SIFT3D_ERR("Dicom.PET_SUV_multiplier: failed to get " "PatientWeight (0010, 1010) from file %s (%s)\n", path, status.text()); return -1.0; } // Read the radiopharmeceutical dosage (0018, 1074) Float64 dosage; status = data->findAndGetFloat64(DCM_RadionuclideTotalDose, dosage, 0, OFTrue); if (status.bad()) { SIFT3D_ERR("Dicom.PET_SUV_multiplier: failed to get " "RadionuclideTotalDose (0018, 1074) from file %s " "(%s)\n", path, status.text()); return -1.0; } // Read the radiopharmeceutical injection time (0018, 1072) // DCM_RadiopharmaceuticalStartTime (TM) const char *timeStr; status = data->findAndGetString(DCM_RadiopharmaceuticalStartTime, timeStr, OFTrue); if (status.bad() || timeStr == NULL) { SIFT3D_ERR("Dicom.PET_SUV_multiplier: failed to get " "RadiopharmeceuticalStartTime (0018, 1072) from file %s" " (%s)\n", path, status.text()); return -1.0; } // Parse the injection time Float64 iv_time; if (parseTM(timeStr, &iv_time)) { SIFT3D_ERR("Dicom.PET_SUV_multipler: failed to parse " "RadiopharmeceuticalStartTime (0018, 1072) value %s from file " "%s\n", timeStr, path); return -1.0; } // Read the image acquistion time (0008, 0032) status = data->findAndGetString(DCM_AcquisitionTime, timeStr); if (status.bad() || timeStr == NULL) { SIFT3D_ERR("Dicom.PET_SUV_multiplier: failed to get " "AcquisitionTime (0008, 0032) from file %s" " (%s)\n", path, status.text()); return -1.0; } // Parse the image time Float64 image_time; if (parseTM(timeStr, &image_time)) { SIFT3D_ERR("Dicom.PET_SUV_multipler: filed to parse " "AcquisitionTime (0008, 0032) value %s from file %s\n", timeStr, path); return -1.0; } // Read the radiopharmeceutical half-life (0018, 1075) Float64 half_life; status = data->findAndGetFloat64(DCM_RadionuclideHalfLife, half_life, 0, OFTrue); if (status.bad()) { SIFT3D_ERR("Dicom.PET_SUV_multiplier: failed to get " "RadionuclideHalfLife (0018, 1075) from file %s (%s)\n", path, status.text()); return -1.0; } // Compute the elapsed time double elapsed_time = iv_time - image_time; if (elapsed_time < 0) { const double day_seconds = 24.0 * 60.0 * 60.0; elapsed_time = elapsed_time + day_seconds; } // Compute the time-adjusted dosage const double adjusted_dosage = dosage * pow(2.0, -elapsed_time / half_life); // Compute the SUV multiplier return weight / adjusted_dosage; } /* Read a DICOM file into an Image struct. If the file is a DSO, the * referenced DICOM image must be in the same directory. */ int read_dcm(const char *path, Image *const im) { int ret; CATCH_EXCEPTIONS(ret, "read_dcm", read_dcm_cpp, path, im); return ret; } /* Read all of the DICOM files from a directory into an Image struct. Slices * must be ordered alphanumerically, starting with z = 0. */ int read_dcm_dir(const char *path, Image *const im) { int ret; CATCH_EXCEPTIONS(ret, "read_dcm_dir", read_dcm_dir_cpp, path, im); return ret; } /* Write an Image struct into a DICOM file. * Inputs: * path - File name * im - Image data * meta - Dicom metadata (or NULL for default values) * max_val - The maximum value of the image, used for scaling. If set * to a negative number, this functions computes the maximum value * from this image. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int write_dcm(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val) { int ret; CATCH_EXCEPTIONS(ret, "write_dcm", write_dcm_cpp, path, im, meta, max_val); return ret; } /* Write an Image struct into a directory of DICOM files. * Inputs: * path - File name * im - Image data * meta - Dicom metadata (or NULL for default values) * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int write_dcm_dir(const char *path, const Image *const im, const Dcm_meta *const meta) { int ret; CATCH_EXCEPTIONS(ret, "write_dcm_dir", write_dcm_dir_cpp, path, im, meta); return ret; } /* Helper function to read a DICOM file using C++ */ static int read_dcm_cpp(const char *path, Image *const im) { // Read the image metadata Dicom dicom(path, 1); if (!dicom.isValid()) return SIFT3D_FAILURE; // Check if this is a segmentation or normal image if (dicom.getType() == DSO) { // Look for images only in the same directory char *pathDir = im_get_parent_dir(path); int ret = read_dso(pathDir, dicom, im); free(pathDir); return ret; } // Otherwise directly read the image data return read_dcm_img(dicom, im); } /* Helper function to read DICOM image data */ static int read_dcm_img(const Dicom &dicom, Image *const im) { int offsets[] = {0, 0, 0}; int signs[] = {1, 1, 1}; const void *data; const DiMonoPixel *pixels; uint32_t shift; int x, y, z, depth, k; const int bufNBits = 32; // Image data strides const int y_stride = dicom.getNx(); const int z_stride = dicom.getNx() * dicom.getNy(); // Initialize JPEG decoders DJDecoderRegistration::registerCodecs(); // Initialize the DicomImage object const char *path = dicom.name(); DicomImage dicomImage(path); if (dicomImage.getStatus() != EIS_Normal) { SIFT3D_ERR("read_dcm_cpp: failed to open image %s (%s)\n", path, DicomImage::getString(dicomImage.getStatus())); goto read_dcm_img_quit; } // Initialize the image fields im->nx = dicom.getNx(); im->ny = dicom.getNy(); im->nz = dicom.getNz(); im->nc = dicom.getNc(); im->ux = dicom.getUx(); im->uy = dicom.getUy(); im->uz = dicom.getUz(); // Resize the output im_default_stride(im); if (im_resize(im)) goto read_dcm_img_quit; // Format the offsets and signs for the main volume for (k = 0; k < 2; k++) { if (dicom.getAxisSign(k) > 0) continue; const int axisIdx = dicom.getAxes(k); signs[axisIdx] = -1; offsets[axisIdx] = dicom.getDim(axisIdx) - 1; } // Get the vendor-independent intermediate pixel data pixels = (const DiMonoPixel *) dicomImage.getInterData(); if (pixels == NULL) { SIFT3D_ERR("read_dcm_img: failed to get intermediate data for " "%s\n", path); goto read_dcm_img_quit; } depth = pixels->getBits(); // Macro to copy the data #define COPY_DATA(type) { \ SIFT3D_IM_LOOP_START(im, x, y, z) \ SIFT3D_IM_GET_VOX(im, x * signs[0] + offsets[0], \ y * signs[1] + offsets[1], \ z * signs[2] + offsets[2], \ 0) = (float) *((type *) data + x + y * y_stride + \ z * z_stride);\ SIFT3D_IM_LOOP_END \ }\ // Choose the appropriate data type and copy the data data = pixels->getData(); switch (pixels->getRepresentation()) { case EPR_Uint8: COPY_DATA(uint8_t) break; case EPR_Uint16: COPY_DATA(uint16_t) break; case EPR_Uint32: COPY_DATA(uint32_t) break; case EPR_Sint8: COPY_DATA(int8_t) break; case EPR_Sint16: COPY_DATA(int16_t) break; case EPR_Sint32: COPY_DATA(int32_t) break; default: SIFT3D_ERR("read_dcm_img: unrecognized pixel representation " "for %s\n", path); goto read_dcm_img_quit; } #undef COPY_DATA #if 0 // Get the bit depth of the image depth = dicomImage.Depth(); if (depth > bufNBits) SIFT3D_ERR("read_dcm_cpp: buffer is insufficiently wide " "for %d-bit data of image %s \n", depth, path); goto read_dcm_img_quit; } // Get the number of bits by which we need to shift the 32-bit data, to // recover the original resolution (DICOM uses Big-endian encoding) shift = isLittleEndian() ? static_cast(bufNBits - depth) : 0; // Read each frame for (int i = 0; i < im->nz; i++) { int x, y, z; const int x_start = 0; const int y_start = 0; const int z_start = i; const int x_end = im->nx - 1; const int y_end = im->ny - 1; const int z_end = z_start; // Get a pointer to the data, rendered as a 32-bit int const uint32_t *const frameData = static_cast( dicomImage.getOutputData( static_cast(bufNBits), i)); if (frameData == NULL) { SIFT3D_ERR("read_dcm_cpp: could not get data from " "image %s frame %d (%s)\n", path, i, DicomImage::getString(dicomImage.getStatus())); goto read_dcm_img_quit; } // Copy the frame SIFT3D_IM_LOOP_LIMITED_START(im, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end) // Get the voxel and shift it to match the original // magnitude const uint32_t vox = frameData[x + y * im->nx] >> shift; // Convert to float and write to the output image SIFT3D_IM_GET_VOX(im, x, y, z, 0) = static_cast(vox); SIFT3D_IM_LOOP_END } #endif // Clean up DJDecoderRegistration::cleanup(); // Modality-specific post-processing if (dicom.getType() == PET) { const double suv_factor = dicom.PET_SUV_multiplier(); // Check for errors in SUV computation if (suv_factor < 0) return SIFT3D_FAILURE; // Scale all the values SIFT3D_IM_LOOP_START(im, x, y, z) SIFT3D_IM_GET_VOX(im, x, y, z, 0) *= suv_factor; SIFT3D_IM_LOOP_END } return SIFT3D_SUCCESS; read_dcm_img_quit: DJDecoderRegistration::cleanup(); return SIFT3D_FAILURE; } /* Read a DICOM Segmentation Object (DSO) mask. * * Parameters: * imDir - The path of the directory containing DICOM image files. * dsoPath - The path of the DSO file. * mask - The image in which to write a binary mask. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int read_dso(const char *imDir, Dicom &dso, Image *const mask) { // Read the DSO DcmFileFormat fileFormat; const char *dsoPath = dso.name(); if (load_file(dso.name(), fileFormat)) return SIFT3D_FAILURE; DcmDataset *const dso_data = fileFormat.getDataset(); // Initialize the DcmSegmentation object OFCondition status; OFunique_ptr dcmSegmentation; { DcmSegmentation *p; status = DcmSegmentation::loadDataset(*dso_data, p); dcmSegmentation.reset(p); } if (status.bad()) { SIFT3D_ERR("read_dso: failed to initialize the " "DcmSegmentation object for file %s (%s)\n", dsoPath, status.text()); return SIFT3D_FAILURE; } // Ensure we have only one segment const int num_segments = dcmSegmentation->getNumberOfSegments(); if (num_segments != 1) { SIFT3D_ERR("read_dso: unsupported number of segments " " in file %s: %d\n", dsoPath, num_segments); return SIFT3D_FAILURE; } // Check how many frames we have const int nz = dcmSegmentation->getNumberOfFrames(); // Get the referenced series items OFVector& series_items = dcmSegmentation->getCommonInstanceReference() .getReferencedSeriesItems(); // Ensure there is only one series const int num_series = series_items.size(); if (num_series != 1) { SIFT3D_ERR("read_dso: unsupported number of referenced " "series: %d \n", num_series); return SIFT3D_FAILURE; } // Get the referenced instance items OFVector &ref_instances = series_items[0]->getReferencedInstanceItems(); // Read the image metadata std::vector images; if (read_dcm_dir_meta(imDir, images)) return SIFT3D_FAILURE; // Read the mask DICOM image data Image maskData; init_im(&maskData); if (read_dcm_img(dso, &maskData)) { im_free(&maskData); return SIFT3D_FAILURE; } // Ensure we have a single frame for each instance if (ref_instances.size() != maskData.nz) { SIFT3D_ERR("read_dso: DSO has %lu frames but %d segments \n", ref_instances.size(), maskData.nz); im_free(&maskData); return SIFT3D_FAILURE; } // Initialize and zero the mask if (dcm_resize_im(images, mask)) { im_free(&maskData); return SIFT3D_FAILURE; } im_zero(mask); // Ensure the x,y dimensions match if (mask->nx != maskData.nx || mask->ny != maskData.ny) { SIFT3D_ERR("read_dso: Mask has frame dimensions [%d x %d] " "while image volume has dimensions [%d x %d] \n", maskData.nx, maskData.ny, mask->nx, mask->ny); im_free(&maskData); return SIFT3D_FAILURE; } // Copy each frame into the mask for (auto it = ref_instances.begin(); it != ref_instances.end(); ++it) { // Get the SOPInstanceUID OFString ref_uid; status = (*it)->getReferencedSOPInstanceUID(ref_uid); if (status.bad()) { SIFT3D_ERR("read_dso: Failed to get " "ReferencedSOPInstanceUID (%s) \n", status.text()); im_free(&maskData); return SIFT3D_FAILURE; } const char *ref_uid_str = ref_uid.c_str(); // Check for a matching UID in the images auto match = std::find_if( images.begin(), images.end(), [ref_uid_str] (Dicom &image) { return image.eqInstance(ref_uid_str); } ); if (match == images.end()) { SIFT3D_ERR("read_dso: No image found with " "SOPInstanceUID %s \n", ref_uid_str); im_free(&maskData); return SIFT3D_FAILURE; } // Get the z offsets const int im_frame_num = std::distance(images.begin(), match); const int dso_frame_num = std::distance(ref_instances.begin(), it); // Copy the frame into the full mask volume if (write_subvolume(*match, mask, im_frame_num, &maskData, dso_frame_num, dso_frame_num)) { im_free(&maskData); return SIFT3D_FAILURE; } } // Clean up im_free(&maskData); return SIFT3D_SUCCESS; } /* Helper function to read the metadata from a directory of DICOM files */ static int read_dcm_dir_meta(const char *path, std::vector &dicoms) { struct stat st; DIR *dir; struct dirent *ent; // Verify that the directory exists if (stat(path, &st)) { SIFT3D_ERR("read_dcm_dir_cpp: cannot find file %s \n", path); return SIFT3D_FAILURE; } else if (!S_ISDIR(st.st_mode)) { SIFT3D_ERR("read_dcm_dir_cpp: file %s is not a directory \n", path); return SIFT3D_FAILURE; } // Open the directory if ((dir = opendir(path)) == NULL) { SIFT3D_ERR("read_dcm_dir_cpp: unexpected error opening " "directory %s \n", path); return SIFT3D_FAILURE; } // Get all of the .dcm files in the directory, ignoring DSOs dicoms.clear(); while ((ent = readdir(dir)) != NULL) { // Form the full file path std::string fullfile(std::string(path) + sepStr + ent->d_name); // Check if it is a DICOM file if (im_get_format(fullfile.c_str()) != DICOM) continue; // Read the file Dicom dicom(fullfile.c_str(), 0); if (!dicom.isValid()) { closedir(dir); return SIFT3D_FAILURE; } // Ignore DSOs if (dicom.getType() == DSO) continue; // Add the file to the list dicoms.push_back(dicom); } // Release the directory closedir(dir); // Verify that dicom files were found if (dicoms.size() == 0) { SIFT3D_ERR("read_dcm_dir_cpp: no DICOM files found in %s \n", path); return SIFT3D_FAILURE; } // Sort the slices by their coordinates std::sort(dicoms.begin(), dicoms.end()); return SIFT3D_SUCCESS; } /* Resize an image to fit a DICOM series. */ static int dcm_resize_im(const std::vector &dicoms, Image *const im) { int i, j; // Verify that the files are from the same series, and other metadata const int num_files = dicoms.size(); const Dicom &first = dicoms[0]; const int sortAxis = first.getSortAxis(); for (int i = 1; i < num_files; i++) { const Dicom &dicom = dicoms[i]; // Verify the SOPSeriesUID if (!first.eqSeries(dicom)) { SIFT3D_ERR("read_dcm_dir_cpp: file %s is from a " "different series than file %s \n", dicom.name(), first.name()); return SIFT3D_FAILURE; } // Verify the sorting axis if (dicom.getSortAxis() != sortAxis) { SIFT3D_ERR("read_dcm_dir_cpp: file %s (%d) is sorted " "by a different axis than file %s (%d) \n", dicom.name(), dicom.getSortAxis(), first.name(), sortAxis); return SIFT3D_INCONSISTENT_AXES; } } // Initialize the units to those of the first image im->ux = first.getUx(); im->uy = first.getUy(); im->uz = first.getUz(); // Compute the spacing between slices if (num_files > 1) { // Tolerance for spacing discrepancies const double tol_spacing = 5E-2; // If there are multiple slices, compute the slice spacing by // subtracting the first two images const double first_spacing = fabs(first.getSortCoord() - dicoms[1].getSortCoord()); // Ensure all the other spacings agree, else throw an error for (int i = 0; i < num_files - 1; i++) { const Dicom &prev = dicoms[i]; const Dicom &next = dicoms[i + 1]; const double spacing = fabs(prev.getSortCoord() - next.getSortCoord()); // Check for duplicates if (spacing == 0.0) { SIFT3D_ERR("read_dcm_dir_cpp: files %s and %s " "have duplicate coordinates in the sorting " "dimension (%d) \n", prev.name(), next.name(), prev.getSortAxis()); return SIFT3D_DUPLICATE_SLICES; } // Check the spacing if (fabs(spacing - first_spacing) <= tol_spacing) continue; SIFT3D_ERR("read_dcm_dir_cpp: files %s and %s are " "spaced %fmm apart, which does not follow the " "series spacing of %fmm \n", prev.name(), next.name(), spacing, first_spacing); return SIFT3D_UNEVEN_SPACING; } // Ensure all the slice thicknesses agree, else print a warning for (int i = 1; i < num_files; i++) { const Dicom &dicom = dicoms[i]; const double thickness = dicom.getSortUnit(); if (fabs(first_spacing - thickness) <= tol_spacing) continue; SIFT3D_ERR("read_dcm_dir_cpp: WARNING--file %s has " "a slice thickness of %fmm, which does not " "agree with the series slice spacing of %fmm\n", dicom.name(), thickness, first_spacing); // No need for multiple warnings break; } SIFT3D_IM_GET_UNITS(im)[sortAxis] = first_spacing; } // Initialize the output dimensions to those of the first image int dims[] = {first.getNx(), first.getNy(), first.getNz()}; int nc = first.getNc(); // Verify the dimensions of the other files, counting the total // number of slices int nSlice = 0; for (i = 0; i < num_files; i++) { // Get a slice const Dicom &dicom = dicoms[i]; // Verify the channels if (dicom.getNc() != nc) { SIFT3D_ERR("read_dcm_dir_cpp: slice %s " "(%d) does not match the " "channels of slice %s (%d) \n", dicom.name(), dicom.getNc(), first.name(), nc); } // Verify the non-sorting dimensions for (j = 0; j < 2; j++) { const int firstDim = dims[j]; const int sliceDim = dicom.getDim(j); if (sliceDim == firstDim) continue; SIFT3D_ERR("read_dcm_dir_cpp: slice %s " "dimension %d (%d) does not match the " "dimensions of slice %s (%d) \n", dicom.name(), sliceDim, sortAxis, first.name(), firstDim); return SIFT3D_FAILURE; } // Count the number of slices nSlice += dicom.getDim(sortAxis); } // Set the dimension of the sorting axis dims[sortAxis] = nSlice; // Resize the output memcpy(SIFT3D_IM_GET_DIMS(im), dims, sizeof(dims)); im->nc = nc; im_default_stride(im); if (im_resize(im)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Copy a Dicom sub-volume into a larger volume. start and end are indices into * sub, along the sorting dimension. */ static int write_subvolume(const Dicom &dicom, Image *const main, const int mainOffset, Image *const sub, const int start, const int end) { int mainOffsets[] = {0, 0, 0}; int starts[] = {0, 0, 0}; int ends[] = {sub->nx - 1, sub->ny - 1, sub->nz - 1}; int x, y, z, c, k; const int sortAxis = dicom.getSortAxis(); const int mainSortDim = SIFT3D_IM_GET_DIMS(main)[sortAxis]; const int subSortDim = SIFT3D_IM_GET_DIMS(sub)[sortAxis]; // Verify the image axis dimensions for (k = 0; k < 2; k++) { const int axisIdx = dicom.getAxes(k); const int mainDim = SIFT3D_IM_GET_DIMS(main)[axisIdx]; const int subDim = SIFT3D_IM_GET_DIMS(sub)[axisIdx]; if (mainDim != subDim) { SIFT3D_ERR("write_subvolume: axis %d does not match: " "%d (main) vs %d (sub)", axisIdx, mainDim, subDim); return SIFT3D_FAILURE; } } // Verify the sorting axis dimensions if (start < 0) { SIFT3D_ERR("write_subvolume: invalid start %d\n", start); return SIFT3D_FAILURE; } if (end > subSortDim) { SIFT3D_ERR("write_subvolume: invalid end %d (maximum %d)\n", end, subSortDim); return SIFT3D_FAILURE; } if (mainSortDim < mainOffset + end - start) { SIFT3D_ERR("write_subvolume: subvolume range (%d, %d) does " "not fit into main volume dimension %d, offset %d, " "axis %d\n", start, end, mainSortDim, mainOffset, sortAxis); return SIFT3D_FAILURE; } // Set the starting and ending indices for the sort axis starts[sortAxis] = start; ends[sortAxis] = end; mainOffsets[sortAxis] = mainOffset; // Copy the data SIFT3D_IM_LOOP_LIMITED_START_C(sub, x, y, z, c, starts[0], ends[0], starts[1], ends[1], starts[2], ends[2]) SIFT3D_IM_GET_VOX(main, x + mainOffsets[0], y + mainOffsets[1], z + mainOffsets[2], c) = SIFT3D_IM_GET_VOX(sub, x, y, z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Helper function to read a directory of DICOM files using C++ */ static int read_dcm_dir_cpp(const char *path, Image *const im) { int ret, i, j, sliceCount; // Read the DICOM metadata std::vector dicoms; if (read_dcm_dir_meta(path, dicoms)) return SIFT3D_FAILURE; // Initialize the image volume if (ret = dcm_resize_im(dicoms, im)) return ret; // Allocate a temporary image for the slices Image slice; init_im(&slice); // Read the image data sliceCount = 0; const int num_files = dicoms.size(); for (i = 0; i < num_files; i++) { Dicom dicom = dicoms[i]; // Read the slice data and write it into the volume const int numSlices = dicom.getDim(dicom.getSortAxis()); if (read_dcm_img(dicom, &slice) || write_subvolume(dicom, im, sliceCount, &slice, 0, numSlices - 1)) { im_free(&slice); return SIFT3D_FAILURE; } sliceCount += numSlices; } im_free(&slice); return SIFT3D_SUCCESS; } /* Helper function to set meta_new to default values if meta is NULL, * otherwise copy meta to meta_new */ static void set_meta_defaults(const Dcm_meta *const meta, Dcm_meta *const meta_new) { if (meta == NULL) { default_Dcm_meta(meta_new); } else { *meta_new = *meta; } } /* Helper function to write a DICOM file using C++ */ static int write_dcm_cpp(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val) { #define BUF_LEN 1024 char buf[BUF_LEN]; // Ensure the image is monochromatic if (im->nc != 1) { SIFT3D_ERR("write_dcm_cpp: image %s has %d channels. " "Currently only single-channel images are supported.\n", path, im->nc); return SIFT3D_FAILURE; } // If no metadata was provided, initialize default metadata Dcm_meta meta_new; set_meta_defaults(meta, &meta_new); // Create a new fileformat object DcmFileFormat fileFormat; // Set the file type to derived DcmDataset *const dataset = fileFormat.getDataset(); OFCondition status = dataset->putAndInsertString(DCM_ImageType, "DERIVED"); if (status.bad()) { std::cerr << "write_dcm_cpp: Failed to set the image type" << std::endl; return SIFT3D_FAILURE; } // Set the class UID dataset->putAndInsertString(DCM_SOPClassUID, UID_CTImageStorage); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the SOPClassUID\n"); return SIFT3D_FAILURE; } // Set the photometric interpretation const char *photoInterp; if (im->nc == 1) { photoInterp = "MONOCHROME2"; } else if (im->nc == 3) { photoInterp = "RGB"; } else { SIFT3D_ERR("write_dcm_cpp: failed to determine the " "photometric representation for %d channels \n", im->nc); return SIFT3D_FAILURE; } dataset->putAndInsertString(DCM_PhotometricInterpretation, photoInterp); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the photometric " "interpretation \n"); return SIFT3D_FAILURE; } // Set the pixel representation to unsigned dataset->putAndInsertUint16(DCM_PixelRepresentation, 0); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel " "representation \n"); return SIFT3D_FAILURE; } // Set the number of channels (Samples Per Pixel) and set the planar // configuration to interlaced pixels assert(SIFT3D_IM_GET_IDX(im, 0, 0, 0, 1) == SIFT3D_IM_GET_IDX(im, 0, 0, 0, 0) + 1); snprintf(buf, BUF_LEN, "%d", im->nc); dataset->putAndInsertString(DCM_SamplesPerPixel, buf); dataset->putAndInsertString(DCM_PlanarConfiguration, "0"); // Set the bits allocated and stored, in big endian format const unsigned int dcm_high_bit = dcm_bit_width - 1; dataset->putAndInsertUint16(DCM_BitsAllocated, dcm_bit_width); dataset->putAndInsertUint16(DCM_BitsStored, dcm_bit_width); dataset->putAndInsertUint16(DCM_HighBit, dcm_high_bit); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the bit widths \n"); return SIFT3D_FAILURE; } // Set the patient name status = dataset->putAndInsertString(DCM_PatientName, meta_new.patient_name); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the patient name\n"); return SIFT3D_FAILURE; } // Set the patient ID status = dataset->putAndInsertString(DCM_PatientID, meta_new.patient_id); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the patient ID \n"); return SIFT3D_FAILURE; } // Set the study UID status = dataset->putAndInsertString(DCM_StudyInstanceUID, meta_new.study_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "StudyInstanceUID \n"); return SIFT3D_FAILURE; } // Set the series UID status = dataset->putAndInsertString(DCM_SeriesInstanceUID, meta_new.series_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "SeriesInstanceUID \n"); return SIFT3D_FAILURE; } // Set the series description status = dataset->putAndInsertString(DCM_SeriesDescription, meta_new.series_descrip); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the series " "description \n"); return SIFT3D_FAILURE; } // Set the instance UID status = dataset->putAndInsertString(DCM_SOPInstanceUID, meta_new.instance_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to set the " "SOPInstanceUID \n"); return SIFT3D_FAILURE; } // Set the dimensions OFCondition xstatus = dataset->putAndInsertUint16(DCM_Rows, im->ny); OFCondition ystatus = dataset->putAndInsertUint16(DCM_Columns, im->nx); snprintf(buf, BUF_LEN, "%d", im->nz); OFCondition zstatus = dataset->putAndInsertString(DCM_NumberOfFrames, buf); if (xstatus.bad() || ystatus.bad() || zstatus.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the dimensions \n"); return SIFT3D_FAILURE; } // Set the instance number snprintf(buf, BUF_LEN, "%u", meta_new.instance_num); status = dataset->putAndInsertString(DCM_InstanceNumber, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the instance " "number \n"); return SIFT3D_FAILURE; } // Set the ImagePositionPatient vector const double imPosX = static_cast(im->nx - 1) * im->ux; const double imPosY = static_cast(im->ny - 1) * im->uy; const double imPosZ = static_cast(meta_new.instance_num) * im->uz; snprintf(buf, BUF_LEN, imPosPatientFmt, imPosX, imPosY, imPosZ); status = dataset->putAndInsertString(DCM_ImagePositionPatient, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "ImagePositionPatient vector \n"); return SIFT3D_FAILURE; } // Set the ImageOrientationPatient vectors const float imageOriPatient[] = {1., 0, 0, 0, 1., 0}; snprintf(buf, BUF_LEN, imOriPatientFmt, imageOriPatient[0], imageOriPatient[1], imageOriPatient[2], imageOriPatient[3], imageOriPatient[4], imageOriPatient[5]); status = dataset->putAndInsertString(DCM_ImageOrientationPatient, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "ImageOrientationPatient vectors \n"); return SIFT3D_FAILURE; } // Set the slice location snprintf(buf, BUF_LEN, "%f", imPosZ); status = dataset->putAndInsertString(DCM_SliceLocation, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the slice " "location \n"); return SIFT3D_FAILURE; } // Set the pixel spacing snprintf(buf, BUF_LEN, pixelSpacingFmt, im->ux, im->uy); status = dataset->putAndInsertString(DCM_PixelSpacing, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel " "spacing \n"); return SIFT3D_FAILURE; } // Set the aspect ratio snprintf(buf, BUF_LEN, "%f\\%f", im->ux, im->uy); status = dataset->putAndInsertString(DCM_PixelAspectRatio, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel aspect " "aspect ratio \n"); return SIFT3D_FAILURE; } // Set the slice thickness snprintf(buf, BUF_LEN, "%f", im->uz); status = dataset->putAndInsertString(DCM_SliceThickness, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the slice " "thickness \n"); return SIFT3D_FAILURE; } // Count the number of pixels in the image unsigned long numPixels = SIFT3D_IM_GET_DIMS(im)[0]; for (int i = 1; i < IM_NDIMS; i++) { numPixels *= SIFT3D_IM_GET_DIMS(im)[i]; } // Get the image scaling factor const float dcm_max_val = static_cast(1 << dcm_bit_width) - 1.0f; const float im_max = max_val < 0.0f ? im_max_abs(im) : max_val; const float scale = im_max == 0.0f ? 1.0f : dcm_max_val / im_max; // Render the data to an 8-bit unsigned integer array assert(dcm_bit_width == 8); assert(fabsf(dcm_max_val - 255.0f) < FLT_EPSILON); uint8_t *pixelData = new uint8_t[numPixels]; int x, y, z, c; SIFT3D_IM_LOOP_START_C(im, x, y, z, c) const float vox = SIFT3D_IM_GET_VOX(im, x, y, z, c); if (vox < 0.0f) { SIFT3D_ERR("write_dcm_cpp: Image cannot be " "negative \n"); return SIFT3D_FAILURE; } pixelData[c + x + y * im->nx + z * im->nx * im->ny] = static_cast(vox * scale); SIFT3D_IM_LOOP_END_C // Write the data status = dataset->putAndInsertUint8Array(DCM_PixelData, pixelData, numPixels); delete[] pixelData; if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to set the pixel data \n"); return SIFT3D_FAILURE; } // Choose the encoding format #if 0 DJEncoderRegistration::registerCodecs(); const E_TransferSyntax xfer = EXS_JPEGProcess14SV1TransferSyntax; DJ_RPLossless rp_lossless; status = dataset->chooseRepresentation(xfer, &rp_lossless); #else const E_TransferSyntax xfer = EXS_LittleEndianExplicit; dataset->chooseRepresentation(xfer, NULL); #endif if (!dataset->canWriteXfer(xfer)) { SIFT3D_ERR("write_dcm_cpp: Failed to choose the encoding " "format \n"); return SIFT3D_FAILURE; } // Force the media storage UIDs to be re-generated by removing them dataset->remove(DCM_MediaStorageSOPClassUID); dataset->remove(DCM_MediaStorageSOPInstanceUID); // Save the file status = fileFormat.saveFile(path, xfer); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to write file %s (%s) \n", path, status.text()); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; #undef BUF_LEN } /* Helper function to write an image to a directory of DICOM files using C++ */ static int write_dcm_dir_cpp(const char *path, const Image *const im, const Dcm_meta *const meta) { Image slice; // Initialize C intermediates init_im(&slice); // Initialize the metadata to defaults, if it is null Dcm_meta meta_new; set_meta_defaults(meta, &meta_new); // Get the number of leading zeros for the file names const int num_slices = im->nz; const int num_zeros = static_cast(ceil(log10( static_cast(num_slices)))); // Form the printf format string for file names #define BUF_LEN 16 char format[BUF_LEN]; snprintf(format, BUF_LEN, "%%0%dd.%s", num_zeros, ext_dcm); #undef BUF_LEN // Resize the slice buffer slice.nx = im->nx; slice.ny = im->ny; slice.nz = 1; slice.nc = im->nc; im_default_stride(&slice); if (im_resize(&slice)) { im_free(&slice); return SIFT3D_FAILURE; } // Copy the units to the slice memcpy(SIFT3D_IM_GET_UNITS(&slice), SIFT3D_IM_GET_UNITS(im), IM_NDIMS * sizeof(double)); // Get the maximum absolute value of the whole image volume const float max_val = im_max_abs(im); // Write each slice for (int i = 0; i < num_slices; i++) { // Form the slice file name #define BUF_LEN 1024 char buf[BUF_LEN]; snprintf(buf, BUF_LEN, format, i); // Form the full file path std::string fullfile(path + sepStr + buf); // Copy the data to the slice int x, y, z, c; SIFT3D_IM_LOOP_START_C(&slice, x, y, z, c) SIFT3D_IM_GET_VOX(&slice, x, y, z, c) = SIFT3D_IM_GET_VOX(im, x, y, i, c); SIFT3D_IM_LOOP_END_C // Generate a new SOPInstanceUID dcmGenerateUniqueIdentifier(meta_new.instance_uid, SITE_INSTANCE_UID_ROOT); // Set the instance number const unsigned int instance = static_cast(i + 1); meta_new.instance_num = instance; // Write the slice to a file if (write_dcm(fullfile.c_str(), &slice, &meta_new, max_val)) { im_free(&slice); return SIFT3D_FAILURE; } } // Clean up im_free (&slice); return SIFT3D_SUCCESS; } #endif ================================================ FILE: imutil/dicom.h ================================================ /* ----------------------------------------------------------------------------- * dicom.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Internal header file for the DCMTK wrapper. * ----------------------------------------------------------------------------- */ #ifndef _DICOM_H #define _DICOM_H #ifdef __cplusplus extern "C" { #endif /* Length of UID buffers */ #define SIFT3D_UID_LEN 1024 /* Dicom file extension */ const char ext_dcm[] = "dcm"; /* Internal struct to hold limited Dicom metadata */ typedef struct _Dcm_meta { const char *patient_name; // Patient name const char *patient_id; // Patient ID const char *series_descrip; // Series description char study_uid[SIFT3D_UID_LEN]; // Study Instance UID char series_uid[SIFT3D_UID_LEN]; // Series UID char instance_uid[SIFT3D_UID_LEN]; // SOP Instance UID int instance_num; // Instance number } Dcm_meta; int read_dcm(const char *path, Image *const im); int read_dcm_dir(const char *path, Image *const im); int write_dcm(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val); int write_dcm_dir(const char *path, const Image *const im, const Dcm_meta *const meta); #ifdef __cplusplus } #endif #endif ================================================ FILE: imutil/immacros.h ================================================ /* ----------------------------------------------------------------------------- * immacros.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This header defines preprocessor macros for the imutil library. * ----------------------------------------------------------------------------- */ #include "imtypes.h" #ifdef SIFT3D_MEX #include #include "mex.h" #else #include "stdio.h" #endif #ifndef _IMMACROS_H #define _IMMACROS_H #ifdef __cplusplus extern "C" { #endif // Print an error message #ifdef SIFT3D_MEX #define SIFT3D_ERR(...) \ mexWarnMsgIdAndTxt("sift3d:internal", __VA_ARGS__) #else #define SIFT3D_ERR(...) fprintf(stderr, __VA_ARGS__) #endif // Math macros #define SIFT3D_MIN(x, y) ((x) < (y) ? (x) : (y)) #define SIFT3D_MAX(x, y) ((x) > (y) ? (x) : (y)) #define SIFT3D_AZ_MAX_F (2 * (float) M_PI) // Maximum azimuth #define SIFT3D_PO_MAX_F ((float) M_PI) // Maximum polar angle // Compiler flags #ifdef __GNUC__ #define SIFT3D_IGNORE_UNUSED __attribute__((unused)) #endif // Get a pointer to the [nx, ny, nz] array of an image #define SIFT3D_IM_GET_DIMS(im) \ (&(im)->nx) // Get a pointer to the [xs, ys, zs] array of an image #define SIFT3D_IM_GET_STRIDES(im) \ (&(im)->xs) // Get a pointer to the [ux, uy, uz] array of an image #define SIFT3D_IM_GET_UNITS(im) \ (&(im)->ux) // Get the index of an [x,y,z] pair in an image #define SIFT3D_IM_GET_IDX(im, x, y, z, c) ((size_t) (x) * (im)->xs + \ (size_t) (y) * (im)->ys + (size_t) (z) * (im)->zs + (size_t) (c)) // Get the value of voxel [x,y,z] in an image #define SIFT3D_IM_GET_VOX(im, x, y, z, c) ((im)->data[ \ SIFT3D_IM_GET_IDX((im), (x), (y), (z), (c))]) // Loop through an image in x, z, y order. Delmit with SIFT3D_IM_LOOP_END #define SIFT3D_IM_LOOP_START(im, x, y, z) \ for (z = 0; (z) < (im)->nz; (z)++) { \ for ((y) = 0; (y) < (im)->ny; (y)++) { \ for ((x) = 0; (x) < (im)->nx; (x)++) { /* As in SIFT3D_IM_LOOP_START, but also loop through each channel */ #define SIFT3D_IM_LOOP_START_C(im, x, y, z, c) \ SIFT3D_IM_LOOP_START(im, x, y, z) \ for ((c) = 0; (c) < (im)->nc; (c)++) { /* Loop through an image iterating with the (inclusive) x, y, z bounds given. * Delimit with SIFT3D_IM_LOOP_END. */ #define SIFT3D_IM_LOOP_LIMITED_START(im, x, y, z, x_start, x_end, \ y_start, y_end, z_start, z_end) \ for (z = z_start; (z) <= z_end; (z)++) { \ for ((y) = y_start; (y) <= y_end; (y)++) { \ for ((x) = x_start; (x) <= x_end; (x)++) { /* As in SIFT3D_IM_LOOP_LIMITED_START, but also loop through each channel */ #define SIFT3D_IM_LOOP_LIMITED_START_C(im, x, y, z, c, x_start, x_end, \ y_start, y_end, z_start, z_end) \ SIFT3D_IM_LOOP_LIMITED_START(im, x, y, z, x_start, x_end, \ y_start, y_end, z_start, z_end) \ for ((c) = 0; (c) < (im)->nc; (c)++) { // Delimit an SIFT3D_IM_LOOP_START or SIFT3D_IM_LOOP_LIMITED_START #define SIFT3D_IM_LOOP_END }}} // Delimited an SIFT3D_IM_LOOP_START_C or SIFT3D_IM_LOOP_LIMITED_START_C #define SIFT3D_IM_LOOP_END_C SIFT3D_IM_LOOP_END } /* Check if a point is within the boundaries of an image */ #define IM_CONTAINS(im, x, y, z) \ ((x) >= 0 && (y) >= 0 && (z) >= 0 && (x) < (im)->nx && \ (y) < (im)->ny && (z) < (im)->nz) /* Take the Cartesian gradient of an image at [x, y, z, c]. The voxel cannot be * on the boundary. */ #define SIFT3D_IM_GET_GRAD(im, x, y, z, c, vd) \ (vd)->x = 0.5f * (SIFT3D_IM_GET_VOX(im, (x) + 1, y, z, c) - \ SIFT3D_IM_GET_VOX(im, (x) - 1, y, z, c)); \ (vd)->y = 0.5f * (SIFT3D_IM_GET_VOX(im, x, (y) + 1, z, c) - \ SIFT3D_IM_GET_VOX(im, x, (y) - 1, z, c)); \ (vd)->z = 0.5f * (SIFT3D_IM_GET_VOX(im, x, y, (z) + 1, c) - \ SIFT3D_IM_GET_VOX(im, x, y, (z) - 1, c)) /* Get the Hessian of an image at [x, y, z]. The voxel cannot be on the * boundary. */ #define SIFT3D_IM_GET_HESSIAN(im, x, y, z, c, H, type) \ /* Dxx */ \ SIFT3D_MAT_RM_GET(H, 0, 0, type) = (type) (0.25f * \ (SIFT3D_IM_GET_VOX(im, x + 1, y, z, c) - \ 2 * SIFT3D_IM_GET_VOX(im, x, y, z, c) + \ SIFT3D_IM_GET_VOX(im, x - 1, y, z, c))); \ /* Dxy */ \ SIFT3D_MAT_RM_GET(H, 0, 1, type) = (type) (0.25f * \\ (SIFT3D_IM_GET_VOX(im, x + 1, y + 1, z, c) - \ SIFT3D_IM_GET_VOX(im, x - 1, y + 1, z, c) + \ SIFT3D_IM_GET_VOX(im, x - 1, y - 1, z, c) - \ SIFT3D_IM_GET_VOX(im, x + 1, y - 1, z, c))); \ /* Dxz */ \ SIFT3D_MAT_RM_GET(H, 0, 2, type) = (type) (0.25f * \ (SIFT3D_IM_GET_VOX(im, x + 1, y, z + 1, c) - \ SIFT3D_IM_GET_VOX(im, x - 1, y, z + 1, c) + \ SIFT3D_IM_GET_VOX(im, x - 1, y, z - 1, c) - \ SIFT3D_IM_GET_VOX(im, x + 1, y, z - 1, c))); \ /* Dyx */ \ SIFT3D_MAT_RM_GET(H, 1, 0, type) = SIFT3D_MAT_RM_GET(H, 0, 1, type); \ /* Dyy */ \ SIFT3D_MAT_RM_GET(H, 1, 1, type) = (type) (0.25f * \ (SIFT3D_IM_GET_VOX(im, x, y + 1, z, c) - \ 2 * SIFT3D_IM_GET_VOX(im, x, y, z, c) + \ SIFT3D_IM_GET_VOX(im, x, y - 1, z, c))); \ /* Dyz */ \ SIFT3D_MAT_RM_GET(H, 1, 2, type) = (type) (0.25f * \ (SIFT3D_IM_GET_VOX(im, x, y + 1, z + 1, c) - \ SIFT3D_IM_GET_VOX(im, x, y - 1, z + 1, c) + \ SIFT3D_IM_GET_VOX(im, x, y - 1, z - 1, c) - \ SIFT3D_IM_GET_VOX(im, x, y + 1, z - 1, c))); \ /* Dzx */ \ SIFT3D_MAT_RM_GET(H, 2, 0, type) = SIFT3D_MAT_RM_GET(H, 0, 2, type); \ /* Dzy */ \ SIFT3D_MAT_RM_GET(H, 2, 1, type) = SIFT3D_MAT_RM_GET(H, 1, 2, type); \ /* Dzz */ \ SIFT3D_MAT_RM_GET(H, 2, 2, type) = (type) (0.25f * \ (SIFT3D_IM_GET_VOX(im, x, y, z + 1, c) - \ 2 * SIFT3D_IM_GET_VOX(im, x, y, z, c) + \ SIFT3D_IM_GET_VOX(im, x, y, z - 1, c))) // Get a pointer to an image struct at pyramid level [o, s] #define SIFT3D_PYR_IM_GET(pyr, o, s) ((pyr)->levels + \ ((o) - (pyr)->first_octave) * \ (pyr)->num_levels + ((s) - (pyr)->first_level)) // Get the index of the last octave of a Pyramid struct #define SIFT3D_PYR_LAST_OCTAVE(pyr) \ ((pyr)->first_octave + (pyr)->num_octaves - 1) // Get the index of the last level of a Pyramid struct #define SIFT3D_PYR_LAST_LEVEL(pyr) \ ((pyr)->first_level + (pyr)->num_levels - 1) // Loop through all levels of a given pyramid #define SIFT3D_PYR_LOOP_START(pyr, o, s) \ for ((o) = (pyr)->first_octave; (o) <= SIFT3D_PYR_LAST_OCTAVE(pyr); \ (o)++) { \ for ((s) = (pyr)->first_level; (s) <= SIFT3D_PYR_LAST_LEVEL(pyr); \ (s)++) { // Loop from the specified (inclusive) limits of a given pyramid #define SIFT3D_PYR_LOOP_LIMITED_START(o, s, o_start, o_end, s_start, s_end) \ for ((o) = (o_start); (o) <= (o_end); (o)++) { \ for ((s) = (s_start); (s) <= (s_end); (s)++) { // Delimit a SIFT3D_PYR_LOOP #define SIFT3D_PYR_LOOP_END }} // Delimit the first level of a SIFT3D_PYR_LOOP #define SIFT3D_PYR_LOOP_SCALE_END } // Delimit the second level of a PYR_LOOP #define SIFT3D_PYR_LOOP_OCTAVE_END } // Get a pointer to the incremental Gaussian filter for level s #define SIFT3D_GAUSS_GET(gss, s) \ ((gss)->gauss_octave + (s - (gss)->first_level)) /* Resize a slab. If SIFT3D_SLAB_SIZE is defined, add * elements in increments of that number. Otherwise, * use a default of 500. This macro is meant to be * used whether or not the slab buffer actually needs * resizing -- it checks for that. */ #ifndef SIFT3D_SLAB_LEN #define SIFT3D_SLAB_LEN 500 #endif #define SIFT3D_RESIZE_SLAB(slab, num_new, size) \ { \ const size_t slabs_new = ( (size_t) (num_new) + SIFT3D_SLAB_LEN - 1) / \ SIFT3D_SLAB_LEN; \ const size_t size_new = slabs_new * SIFT3D_SLAB_LEN * (size); \ \ if (size_new != (slab)->buf_size) { \ \ /* Re-initialize if the new size is 0 */ \ if (size_new == 0) { \ cleanup_Slab(slab); \ init_Slab(slab); \ /* Else allocate new memory */ \ } else if (((slab)->buf = SIFT3D_safe_realloc((slab)->buf, \ size_new)) == NULL) { \ return SIFT3D_FAILURE; \ } \ (slab)->buf_size = size_new; \ } \ (slab)->num = (num_new); \ } // Nested loop through all elements of a matrix #define SIFT3D_MAT_RM_LOOP_START(mat, row, col) \ for ((row) = 0; (row) < (mat)->num_rows; (row)++) { \ for ((col) = 0; (col) < (mat)->num_cols; (col)++) { // Delmit a MAT_LOOP #define SIFT3D_MAT_RM_LOOP_END }} // Delimit the first level of a MAT_LOOP #define SIFT3D_MAT_RM_LOOP_COL_END } // Delmit the second level of a MAT_LOOP #define SIFT3D_MAT_RM_LOOP_ROW_END } // Get the index of an element in a dense matrix, in row-major order. #define SIFT3D_MAT_RM_GET_IDX(mat, row, col) \ ((col) + (row) * (mat)->num_cols) // Get an element from a dense matrix in row-major order. Type must // be "double", "float", or "int." #define SIFT3D_MAT_RM_GET(mat, row, col, type) ((mat)->u.data_ ## type \ [SIFT3D_MAT_RM_GET_IDX(mat, row, col)]) /* Execute the macro MACRO, with the first argument set to the type of mat. If * there is an error, goto err_label. */ #define SIFT3D_MAT_RM_TYPE_MACRO(mat, err_label, MACRO, ...) \ switch ((mat)->type) { \ case SIFT3D_DOUBLE: \ MACRO(double, ## __VA_ARGS__) \ break; \ case SIFT3D_FLOAT: \ MACRO(float, ## __VA_ARGS__) \ break; \ case SIFT3D_INT: \ MACRO(int, ## __VA_ARGS__) \ break; \ default: \ SIFT3D_ERR("imutil: unknown matrix type \n"); \ goto err_label; \ } \ // Convert a vector from Cartesian to Spherical coordinates. #define SIFT3D_CVEC_TO_SVEC(cvec, svec) { \ (svec)->mag = sqrtf((cvec)->x * (cvec)->x + (cvec)->y * (cvec)->y + \ (cvec)->z * (cvec)->z); \ (svec)->az = fmodf(atan2f((cvec)->y, (cvec)->x) + SIFT3D_AZ_MAX_F, \ SIFT3D_AZ_MAX_F); \ (svec)->po = fmodf(acosf((cvec)->z / ((svec)->mag + FLT_EPSILON)), \ SIFT3D_PO_MAX_F); \ } // Convert a vector from Spherical to Cartesian coordinates #define SIFT3D_SVEC_TO_CVEC(svec, cvec) { \ (cvec)->x = (svec)->mag * sinf((svec)->po) * cosf((svec)->az); \ (cvec)->y = (svec)->mag * sinf((svec)->po) * sinf((svec)->az); \ (cvec)->z = (svec)->mag * cosf((svec)->po); \ } // Return the L2 norm of a Cartesian coordinate vector #define SIFT3D_CVEC_L2_NORM(cvec) \ sqrtf((cvec)->x * (cvec)->x + (cvec)->y * (cvec)->y + \ (cvec)->z * (cvec)->z) // Return the square of the L2 norm of a Cartesian coordinate vector #define SIFT3D_CVEC_L2_NORM_SQ(cvec) \ ((cvec)->x * (cvec)->x + (cvec)->y * (cvec)->y + \ (cvec)->z * (cvec)->z) // Scale a Cartesian coordinate vector by a constant factor #define SIFT3D_CVEC_SCALE(cvec, a) { \ (cvec)->x = (cvec)->x * a; \ (cvec)->y = (cvec)->y * a; \ (cvec)->z = (cvec)->z * a; \ } // Operate element-wise on two Cartesian coordinate vectors, cc = ca op cb #define SIFT3D_CVEC_OP(ca, cb, op, cc) { \ (cc)->x = (ca)->x op (cb)->x; \ (cc)->y = (ca)->y op (cb)->y; \ (cc)->z = (ca)->z op (cb)->z; \ } // Return the dot product of two Cartesian coordinate // vectors #define SIFT3D_CVEC_DOT(in1, in2) \ ((in1)->x * (in2)->x + (in1)->y * (in2)->y + (in1)->z * (in2)->z) // Take the cross product of two Cartesian coordinate // vectors, as out = in1 X in2 #define SIFT3D_CVEC_CROSS(in1, in2, out) { \ (out)->x = (in1)->y * (in2)->z - (in1)->z * (in2)->y; \ (out)->y = (in1)->z * (in2)->x - (in1)->x * (in2)->z; \ (out)->z = (in1)->x * (in2)->y - (in1)->y * (in2)->x; \ } // Evaluates to true (nonzero) if im contains cvec, false otherwise #define SIFT3D_IM_CONTAINS_CVEC(im, cvec) ( \ (cvec)->x >= 0 || (cvec)->y >= 0 || (cvec)->z >= 0 || \ (cvec)->x < (float) (im)->nx || \ (cvec)->y < (float) (im)->ny || \ (cvec)->z < (float) (im)->nz \ ) /* Computes v_out = mat * v_in. Note that mat must be of FLOAT * type, since this is the only type available for vectors. * Also note that mat must be (3 x 3). */ #define SIFT3D_MUL_MAT_RM_CVEC(mat, v_in, v_out) { \ (v_out)->x = SIFT3D_MAT_RM_GET(mat, 0, 0, float) * (v_in)->x + \ SIFT3D_MAT_RM_GET(mat, 0, 1, float) * (v_in)->y + \ SIFT3D_MAT_RM_GET(mat, 0, 2, float) * (v_in)->z; \ \ (v_out)->y = SIFT3D_MAT_RM_GET(mat, 1, 0, float) * (v_in)->x + \ SIFT3D_MAT_RM_GET(mat, 1, 1, float) * (v_in)->y + \ SIFT3D_MAT_RM_GET(mat, 1, 2, float) * (v_in)->z; \ \ (v_out)->z = SIFT3D_MAT_RM_GET(mat, 2, 0, float) * (v_in)->x + \ SIFT3D_MAT_RM_GET(mat, 2, 1, float) * (v_in)->y + \ SIFT3D_MAT_RM_GET(mat, 2, 2, float) * (v_in)->z; \ } #ifdef __cplusplus } #endif #endif ================================================ FILE: imutil/imtypes.h ================================================ /* ----------------------------------------------------------------------------- * imtypes.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This header contains data type definitions. * ----------------------------------------------------------------------------- */ #include #ifndef _IMTYPES_H #define _IMTYPES_H #ifdef __cplusplus extern "C" { #endif // Return codes #define SIFT3D_SINGULAR 1 #define SIFT3D_SUCCESS 0 #define SIFT3D_FAILURE -1 #define SIFT3D_HELP 1 #define SIFT3D_VERSION 2 // Truth values #define SIFT3D_TRUE 1 #define SIFT3D_FALSE 0 // Platform types #if _Win16 == 1 || _WIN32 == 1 || _WIN64 == 1 || \ defined __WIN32__ || defined __TOS_WIN__ || \ defined __WINDOWS__ && !defined _WINDOWS #define _WINDOWS #endif #if (defined(__MINGW32__) || defined(__MINGW64__)) && defined(_WINDOWS) && \ !defined _MINGW_WINDOWS #define _MINGW_WINDOWS #endif /* OpenCL type definitions. */ #ifdef USE_OPENCL #ifdef _WINDOWS #include "CL\cl.h" #else #include "CL/cl.h" #endif #else /* Use dummy macro definitions */ #define CL_SUCCESS 0 #define CL_FAILURE -1 /* Use dummy type definitions. */ typedef unsigned char cl_uchar; typedef int cl_device_id; typedef int cl_command_queue; typedef int cl_device_type; typedef int cl_platform_id; typedef int cl_context; typedef int cl_image_format; typedef int cl_mem_flags; typedef int cl_kernel; typedef int cl_mem; typedef int cl_mem_object_type; typedef int cl_program; typedef int cl_int; typedef int cl_bool; typedef unsigned int cl_uint; #endif /* File separator character */ #ifdef _WINDOWS #define SIFT3D_FILE_SEP '\\' #else #define SIFT3D_FILE_SEP '/' #endif /* Parameters */ #define NBINS_AZ 8 // Number of bins for azimuthal angles #define NBINS_PO 4 // Number of bins for polar angles #define NHIST_PER_DIM 4 // Number of SIFT descriptor histograms per dimension #define ICOS_HIST // Icosahedral gradient histogram /* Constants */ #define IM_NDIMS 3 // Number of dimensions in an Image #define ICOS_NFACES 20 // Number of faces in an icosahedron #define ICOS_NVERT 12 // Number of vertices in an icosahedron /* Derived constants */ #define DESC_NUM_TOTAL_HIST (NHIST_PER_DIM * NHIST_PER_DIM * NHIST_PER_DIM) #define DESC_NUMEL (DESC_NUM_TOTAL_HIST * HIST_NUMEL) // The number of elements in a gradient histogram #ifdef ICOS_HIST #define HIST_NUMEL (ICOS_NVERT) #else #define HIST_NUMEL (NBINS_AZ * NBINS_PO) #endif /* Supported image file formats */ typedef enum _im_format { ANALYZE, /* Analyze */ DICOM, /* DICOM */ DIRECTORY, /* Directory */ NIFTI, /* NIFTI-1 */ UNKNOWN, /* Not one of the known extensions */ FILE_ERROR /* Error occurred in determining the format */ } im_format; /* Possible data types for matrix elements */ typedef enum _Mat_rm_type { SIFT3D_DOUBLE, SIFT3D_FLOAT, SIFT3D_INT } Mat_rm_type; /* Struct to hold OpenCL programs for this library */ typedef struct _kernels { cl_kernel downsample_2x_3d; } Kernels; /* Struct to hold OpenCL data about the user system */ typedef struct _CL_data { cl_device_id *devices; // num_devices elements cl_command_queue *queues; // One per device cl_platform_id platform; cl_context context; cl_uint num_devices; cl_image_format image_format; cl_mem_flags mem_flags; Kernels kernels; int valid; // Is this struct valid? } CL_data; /* Struct to hold a dense matrix in row-major order */ typedef struct _Mat_rm { union { double *data_double; float *data_float; int *data_int; } u; size_t size; // Size of the buffer, in bytes int num_cols; // Number of columns int num_rows; // Number of rows int static_mem; // Flag for statically-allocated memory Mat_rm_type type; // DOUBLE, FLOAT, or INT } Mat_rm; /* Struct to hold image data. The image is a rectangular prism, * where the bottom-left corner is [0 0 0], the x-stride is 1, * the y-stride is the width in x, and the z-stride is the * size of an xy plane. For convenience use the macros IM_GET_IDX, * IM_GET_VOX, and IM_SET_VOX to manipulate this struct. */ typedef struct _Image { float *data; // Raster of voxel values ~16MB cl_mem cl_image; // Same-sized OpenCL image object double s; // scale-space location size_t size; // Total size in pixels int nx, ny, nz; // Dimensions in x, y, and z double ux, uy, uz; // Real world dimensions in x, y, and z size_t xs, ys, zs; // Stride in x, y, and z int nc; // The number of channels int cl_valid; // If TRUE, cl_image is valid } Image; /* Holds separable FIR filters and programs to apply them */ typedef struct _Sep_FIR_filter { cl_kernel cl_apply_unrolled; // unrolled OpenCL program to apply filter float *kernel; // filter weights int dim; // dimensionality, e.g. 3 for MRI int width; // number of weights int symmetric; // enable symmetric optimizations: FALSE or TRUE } Sep_FIR_filter; /* Holds Gaussian filters */ typedef struct _Gauss_filter { double sigma; Sep_FIR_filter f; } Gauss_filter; /* Holds Gaussian Scale-Space filters */ typedef struct _GSS_filters { Gauss_filter first_gauss; // Used on the very first blur Gauss_filter *gauss_octave; // Array of kernels for one octave int num_filters; // Number of filters for one octave int first_level; // Index of the first scale level } GSS_filters; /* Struct to hold miscellaneous SIFT detector OpenCL kernels */ typedef struct _SIFT_cl_kernels { cl_kernel downsample_2; } SIFT_cl_kernels; /* Struct to hold a scale-space image pyramid */ typedef struct _Pyramid { // Levels in all octaves Image *levels; // Scale-space parameters double sigma_n; double sigma0; int num_kp_levels; // Indexing information -- see immacros.h int first_octave; int num_octaves; int first_level; int num_levels; } Pyramid; /* Struct defining a vector in spherical coordinates */ typedef struct _Svec { float mag; // Magnitude float po; // Polar angle, [0, pi) float az; // Azimuth angle, [0, 2pi) } Svec; /* Struct defining a vector in Cartesian coordinates */ typedef struct _Cvec { float x; float y; float z; } Cvec; /* Slab allocation struct */ typedef struct _Slab { void *buf; // Buffer size_t num; // Number of elements currently in buffer size_t buf_size; // Buffer capactiy, in bytes } Slab; /* Struct defining a keypoint in 3D space. */ typedef struct _Keypoint { float r_data[IM_NDIMS * IM_NDIMS]; // Memory for matrix R, do not use this Mat_rm R; // Rotation matrix into Keypoint space double xd, yd, zd; // sub-pixel x, y, z double sd; // absolute scale int o, s; // pyramid indices } Keypoint; /* Struct to hold keypoints */ typedef struct _Keypoint_store { Keypoint *buf; Slab slab; int nx, ny, nz; // dimensions of first octave } Keypoint_store; /* Struct defining an orientation histogram in * spherical coordinates. */ typedef struct _Hist { float bins[HIST_NUMEL]; } Hist; /* Triangle */ typedef struct _Tri { Cvec v[3]; // Vertices int idx[3]; // Index of each vertex in the solid } Tri; /* Triangle mesh */ typedef struct _Mesh { Tri *tri; // Triangles int num; // Number of triangles } Mesh; /* Struct defining a 3D SIFT descriptor */ typedef struct _SIFT3D_Descriptor { Hist hists[DESC_NUM_TOTAL_HIST]; // Array of orientation histograms double xd, yd, zd, sd; // sub-pixel [x, y, z], absolute scale } SIFT3D_Descriptor; /* Struct to hold SIFT3D descriptors */ typedef struct _SIFT3D_Descriptor_store { SIFT3D_Descriptor *buf; size_t num; int nx, ny, nz; // Image dimensions } SIFT3D_Descriptor_store; /* Struct to hold all parameters and internal data of the * SIFT3D algorithms */ typedef struct _SIFT3D { // Triange mesh Mesh mesh; // Filters for computing the GSS pyramid GSS_filters gss; // Other OpenCL kernels SIFT_cl_kernels kernels; // Gaussian pyramid Pyramid gpyr; // DoG pyramid Pyramid dog; // Image to process Image im; // Parameters double peak_thresh; // Keypoint peak threshold double corner_thresh; // Keypoint corner threshold int dense_rotate; // If true, dense descriptors are rotation-invariant } SIFT3D; /* Geometric transformations that can be applied by this library. */ typedef enum _tform_type { AFFINE, // Affine (linear + constant) TPS // Thin-plate spline } tform_type; /* Interpolation algorithms that can be used by this library. */ typedef enum _interp_type { LINEAR, // N-linear interpolation LANCZOS2 // Lanczos kernel, a = 2 } interp_type; /* Virtual function table for Tform class */ typedef struct _Tform_vtable { int (*copy)(const void *const, void *const); void (*apply_xyz)(const void *const, const double, const double, const double, double *const, double *const, double *const); int (*apply_Mat_rm)(const void *const, const Mat_rm *const, Mat_rm *const); size_t (*get_size)(void); int (*write)(const char *, const void *const); void (*cleanup)(void *const); } Tform_vtable; /* "Abstract class" of transformations */ typedef struct _Tform { tform_type type; // The specific type, e.g. Affine, TPS const Tform_vtable *vtable; // Table of virtual functions } Tform; /* Struct to hold an affine transformation */ typedef struct _Affine { Tform tform; // Abstract parent class Mat_rm A; // Transformation matrix, x' = Ax } Affine; /* Struct to hold a thin-plate spline */ typedef struct _Tps { Tform tform; // Abstract parent class Mat_rm params; // Transformation matrix, dim * number of control point + dim +1 Mat_rm kp_src; // Control point matrix, number of control point * dim int dim; // Dimensionality, e.g. 3 } Tps; /* Struct to hold RANSAC parameters */ typedef struct _Ransac { double err_thresh; //error threshold for RANSAC inliers int num_iter; //number of RANSAC iterations } Ransac; #ifdef __cplusplus } #endif #endif ================================================ FILE: imutil/imutil.c ================================================ /* ----------------------------------------------------------------------------- * imutil.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Miscellaneous utility routines for image processing, linear algebra, and * statistical regression. This library completely defines the Image, * Mat_rm, and Ransac types, among others, and stands apart from the other * source. * ----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "immacros.h" #include "imtypes.h" #include "dicom.h" #include "nifti.h" #include "imutil.h" /* Check for a version number */ #if !defined(SIFT3D_VERSION_NUMBER) #error imutil.c: Must define the preprocessor macro SIFT3D_VERSION_NUMBER #endif /* Stringify a macro name */ #define STR(x) #x /* Stringify the result of a macro expansion */ #define XSTR(x) STR(x) /* zlib definitions */ #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) #include #include #define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) #else #define SET_BINARY_MODE(file) #endif /* Implementation parameters */ //#define SIFT3D_USE_OPENCL // Use OpenCL acceleration #define SIFT3D_RANSAC_REFINE // Use least-squares refinement in RANSAC /* Implement strnlen, if it's missing */ #ifndef SIFT3D_HAVE_STRNLEN size_t strnlen(const char *string, size_t maxlen) { const char *end = memchr (string, '\0', maxlen); return end ? end - string : maxlen; } #endif /* Implement strndup, if it's missing */ #ifndef SIFT3D_HAVE_STRNDUP char *strndup(const char *s, size_t n) { size_t len = strnlen (s, n); char *new = malloc (len + 1); if (new == NULL) return NULL; new[len] = '\0'; return memcpy (new, s, len); } #endif /* SIFT3D version message */ const char version_msg[] = "SIFT3D version " XSTR(SIFT3D_VERSION_NUMBER) " \n" "\n" "Source code available at https://github.com/bbrister/SIFT3D\n" "\n" "Please contact Blaine Rister (blaine@stanford.edu) with questions or " "concerns. \n"; /* Bug message */ const char bug_msg[] = "SIFT3D has encountered an unexpected error. We would appreciate it \n" "if you would report this issue at the following page: \n" " https://github.com/bbrister/SIFT3D/issues \n"; /* Supported file extensions */ extern const char ext_dcm[]; // dicom.h const char ext_analyze[] = "img";; const char ext_gz[] = "gz"; const char ext_nii[] = "nii"; const char ext_dir[] = ""; /* Output file permissions */ const mode_t out_mode = 0755; /* Default parameters */ const double SIFT3D_err_thresh_default = 5.0; const int SIFT3D_num_iter_default = 500; /* Declarations for the virtual function implementations */ static int copy_Affine(const void *const src, void *const dst); static int copy_Tps(const void *const src, void *const dst); static void apply_Affine_xyz(const void *const affine, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out); static void apply_Tps_xyz(const void *const tps, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out); static int apply_Affine_Mat_rm(const void *const affine, const Mat_rm * const mat_in, Mat_rm * const mat_out); static int apply_Tps_Mat_rm(const void *const tps, const Mat_rm * const mat_in, Mat_rm * const mat_out); static size_t Affine_get_size(void); static size_t Tps_get_size(void); static int write_Affine(const char *path, const void *const tform); static int write_Tps(const char *path, const void *const tform); static void cleanup_Affine(void *const affine); static void cleanup_Tps(void *const tps); static int mkpath(const char *path, mode_t mode); /* Virtual function tables */ const Tform_vtable Affine_vtable = { copy_Affine, apply_Affine_xyz, apply_Affine_Mat_rm, Affine_get_size, write_Affine, cleanup_Affine }; const Tform_vtable Tps_vtable = { copy_Tps, apply_Tps_xyz, apply_Tps_Mat_rm, Tps_get_size, write_Tps, cleanup_Tps }; /* Internal macros */ #define TFORM_GET_VTABLE(arg) (((Affine *) arg)->tform.vtable) #define AFFINE_GET_DIM(affine) ((affine)->A.num_rows) /* Global data */ CL_data cl_data; /* LAPACK declarations */ #ifdef SIFT3D_MEX // Set the integer width to Matlab's defined width #include #include "mex.h" typedef mwSignedIndex fortran_int; #ifdef _WINDOWS // Remove underscores from FORTRAN functions #define dlange_ dlange #define dgecon_ dgecon #define dgelss_ dgelss #define dgetrf_ dgetrf #define dgetrs_ dgetrs #define dsyevd_ dsyevd #endif #else typedef int32_t fortran_int; #endif extern double dlange_(const char *, const fortran_int *, const fortran_int *, const double *, const fortran_int *, double *); extern void dgecon_(const char *, const fortran_int *, double *, const fortran_int *, const double *, double *, double *, fortran_int *, fortran_int *); extern void dgelss_(const fortran_int *, const fortran_int *, const fortran_int *, const double *, const fortran_int *, double *, const fortran_int *, double *, const double *, fortran_int *, double *, const fortran_int *, fortran_int *); extern void dgetrf_(const fortran_int *, const fortran_int *, double *, const fortran_int *, fortran_int *, fortran_int *); extern void dgetrs_(const char *, const fortran_int *, const fortran_int *, const double *, const fortran_int *, fortran_int *, double *, const fortran_int *, fortran_int *); extern void dsyevd_(const char *, const char *, const fortran_int *, double *, const fortran_int *, double *, double *, const fortran_int *, fortran_int *, const fortran_int *, fortran_int *); /* Internal helper routines */ static char *read_file(const char *path); static int do_mkdir(const char *path, mode_t mode); static int cross_mkdir(const char *path, mode_t mode); static double resample_linear(const Image * const in, const double x, const double y, const double z, const int c); static double resample_lanczos2(const Image * const in, const double x, const double y, const double z, const int c); static double lanczos(double x, double a); static int check_cl_image_support(cl_context context, cl_mem_flags mem_flags, cl_image_format image_format, cl_mem_object_type image_type); static int compile_cl_program_from_source(cl_program * program, cl_context context, cl_device_id * devices, int num_devices, char **src, int num_str); static int n_choose_k(const int n, const int k, int **ret); static int make_spline_matrix(Mat_rm * src, Mat_rm * src_in, Mat_rm * sp_src, int K_terms, int *r, int dim); static int make_affine_matrix(const Mat_rm *const pts_in, const int dim, Mat_rm *const mat_out); static Mat_rm *extract_ctrl_pts(void *tform, tform_type type); static Mat_rm *extract_ctrl_pts_Tps(Tps * tps); static int solve_system(const Mat_rm *const src, const Mat_rm *const ref, void *const tform); static double tform_err_sq(const void *const tform, const Mat_rm *const src, const Mat_rm *const ref, const int i); static int ransac(const Mat_rm *const src, const Mat_rm *const ref, const Ransac *const ran, void *tform, int **const cset, int *const len); static int convolve_sep(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit); static int convolve_sep_gen(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit); static int convolve_sep_cl(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, int dim, const double unit); static int convolve_sep_sym(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit); static const char *get_file_name(const char *path); static const char *get_file_ext(const char *name); /* Unfinished public routines */ int init_Tps(Tps * tps, int dim, int terms); int resize_Tps(Tps * tps, int num_pts, int dim); /* As realloc, but frees the underlying pointer and returns NULL on error, or * if size is 0 and ptr is non-NULL. */ void *SIFT3D_safe_realloc(void *ptr, size_t size) { void *ret; // Call realloc and handle failures if (size == 0 || (ret = realloc(ptr, size)) == NULL) { if (ptr != NULL) { free(ptr); } return NULL; } return ret; } /* Finish all OpenCL command queues. */ void clFinish_all() { #ifdef SIFT3D_USE_OPENCL int i; for (i = 0; i < cl_data.num_devices; i++) { clFinish(cl_data.queues[i]); } #endif } /* Check the error code and exit on error, unless NDEBUG is * defined. If exiting, prints the error type and the cause, * given by the msg string. */ void check_cl_error(int err, const char *msg) { #ifdef NDEBUG return; #endif switch (err) { case CL_SUCCESS: return; default: printf("unknown OpenCL error %d \n", err); } printf("Exiting due to error in: %s \n", msg); exit(1); } /* Returns SIFT3D_SUCCESS if the specified format is supported for this context, * or SIFT3D_FAILURE if it is not. */ SIFT3D_IGNORE_UNUSED static int check_cl_image_support(cl_context context, cl_mem_flags mem_flags, cl_image_format image_format, cl_mem_object_type image_type) { #ifdef SIFT3D_USE_OPENCL cl_image_format *image_formats; cl_int err; cl_uint num_image_formats; int i, support; err = clGetSupportedImageFormats(context, mem_flags, image_type, 0, NULL, &num_image_formats); if ((image_formats = malloc(num_image_formats * sizeof(cl_image_format))) == NULL) return SIFT3D_FAILURE; err |= clGetSupportedImageFormats(context, mem_flags, image_type, num_image_formats, image_formats, NULL); check_cl_error(err, "2D image formats"); support = SIFT3D_FALSE; for (i = 0; i < num_image_formats; i++) { if (memcmp (image_formats + i, &image_format, sizeof(cl_image_format))) { support = SIFT3D_TRUE; break; } } return (support == SIFT3D_TRUE) ? SIFT3D_SUCCESS : SIFT3D_FAILURE; #else printf ("check_cl_image_support: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Initialize the relevant OpenCL data, using the specified device type, * memory flags, and image format. If no such devices are found, or these * settings are not supported on the device, returns SIFT3D_FAILURE. Returns SIFT3D_SUCCESS * when userData is initialized. * * This library saves a copy of user_cl_data for use with future calls. To change * the settings of the library, call init_cl again. */ int init_cl(CL_data * user_cl_data, const char *platform_name, cl_device_type device_type, cl_mem_flags mem_flags, cl_image_format image_format) { #ifdef SIFT3D_USE_OPENCL Kernels kernels; cl_platform_id *platforms; cl_device_id *devices; char *name, *src; cl_context_properties properties[3]; cl_command_queue *queues; cl_program program; cl_platform_id platform; cl_context context; cl_uint num_platforms, num_devices; cl_int err; cl_bool support; size_t size; int i; // Initialize persistent arrays to NULL devices = NULL; queues = NULL; src = NULL; // Query the available platforms and select the specified one err = clGetPlatformIDs(0, NULL, &num_platforms); if (err != CL_SUCCESS || num_platforms < 1) goto init_cl_quit; if ((platforms = (cl_platform_id *) malloc(num_platforms * sizeof(cl_platform_id))) == NULL) goto init_cl_quit; clGetPlatformIDs(num_platforms, platforms, NULL); name = NULL; platform = (cl_platform_id) NULL; for (i = 0; i < num_platforms; i++) { err = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, 0, NULL, &size); name = (char *)realloc(name, size * sizeof(char)); err = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, size, name, NULL); if (!strcmp(name, platform_name)) { platform = platforms[i]; } } free(platforms); free(name); if (platform == (cl_platform_id) NULL) { printf("init_cl: Failed to find platform %s \n", platform_name); goto init_cl_quit; } // Get the number of devices of the specified type devices = NULL; err = clGetDeviceIDs(platform, device_type, 0, NULL, &num_devices); if (err != CL_SUCCESS && err != CL_DEVICE_NOT_FOUND) { check_cl_error(err, "Create context"); } else if (num_devices > 0) { devices = (cl_device_id *) malloc(num_devices * sizeof(cl_device_id)); err = clGetDeviceIDs(platform, device_type, num_devices, devices, NULL); check_cl_error(err, "Get devices"); } if (num_devices <= 0 || devices == NULL) { puts("init_cl: No OpenCL devices available \n"); goto init_cl_quit; } //TODO: Multiple GPUs on one context does not seem to work. Maybe try multiple // contexts, one per GPU? num_devices = 1; // Create the context properties[0] = CL_CONTEXT_PLATFORM; properties[1] = (cl_context_properties) platform; properties[2] = 0; context = clCreateContext(properties, num_devices, devices, NULL, NULL, &err); check_cl_error(err, "Create context \n"); // Create a command queue on each device if ((queues = malloc(num_devices * sizeof(cl_command_queue))) == NULL) goto init_cl_quit; for (i = 0; i < num_devices; i++) { cl_device_type type; err = clGetDeviceInfo(devices[i], CL_DEVICE_TYPE, sizeof(cl_device_type), &type, NULL); if (type == device_type) { if ((queues[i] = clCreateCommandQueue(context, devices[i], 0, NULL)) == NULL) { goto init_cl_quit; } } } // Query for support of the desired image and memory format on these devices for (i = 0; i < num_devices; i++) { // Query for image support support = CL_FALSE; clGetDeviceInfo(devices[i], CL_DEVICE_IMAGE_SUPPORT, sizeof(cl_bool), &support, NULL); if (support != CL_TRUE) { printf ("init_cl: Images are not supported by device %d \n", i); goto init_cl_quit; } // Query for support of the specified image format in both 2D and 3D images check_cl_image_support(context, mem_flags, image_format, CL_MEM_OBJECT_IMAGE2D); check_cl_image_support(context, mem_flags, image_format, CL_MEM_OBJECT_IMAGE3D); } // Load the kernels from a file if ((src = read_file(KERNELS_PATH)) == NULL) { printf("init_cl: Error reading kernel source file %s", KERNELS_PATH); goto init_cl_quit; } // Compile static programs if (compile_cl_program_from_source (&program, context, devices, num_devices, &src, 1)) goto init_cl_quit; kernels.downsample_2x_3d = clCreateKernel(program, "downsample_2x_3d", &err); check_cl_error(err, "init_cl: create kernels"); clReleaseProgram(program); // Save data to the user and library copies cl_data.platform = platform; cl_data.devices = devices; cl_data.context = context; cl_data.queues = queues; cl_data.num_devices = num_devices; cl_data.image_format = image_format; cl_data.valid = SIFT3D_TRUE; cl_data.kernels = kernels; *user_cl_data = cl_data; return SIFT3D_SUCCESS; init_cl_quit: if (devices != NULL) free(devices); if (queues != NULL) free(queues); if (src != NULL) free(src); return SIFT3D_FAILURE; #else printf("init_cl: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Compile a program from the given source strings, writing the program handle into * the specified pointer. */ SIFT3D_IGNORE_UNUSED static int compile_cl_program_from_source(cl_program * program, cl_context context, cl_device_id * devices, int num_devices, char **src, int num_str) { #ifdef SIFT3D_USE_OPENCL cl_int err; int i; err = CL_SUCCESS; *program = clCreateProgramWithSource(context, 1, (const char **)src, NULL, &err); if (*program == NULL || err != CL_SUCCESS) { puts("Error creating program for static kernels \n"); return SIFT3D_FAILURE; } if ((err = clBuildProgram(*program, 0, NULL, NULL, NULL, NULL)) != CL_SUCCESS) { char log[1 << 15]; puts("Error: Failed to build program \n"); for (i = 0; i < num_devices; i++) { clGetProgramBuildInfo(*program, devices[i], CL_PROGRAM_BUILD_LOG, sizeof(log), log, NULL); printf("\n-------Build log for device %d-------\n %s", i, log); } return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; #else printf ("compile_cl_program_from_source: This verison was not compiled with " "OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Initialize a triangle mesh for first use. This must be called before mesh * can be used in any other functions. */ void init_Mesh(Mesh * const mesh) { mesh->tri = NULL; mesh->num = -1; } /* Release all memory associated with a triangle mesh. mesh cannot be reused * before it is reinitialized. */ void cleanup_Mesh(Mesh * const mesh) { free(mesh->tri); } /* Convert a matrix to a different type. in and out may be the same pointer. * * This function resizes out. * * All matrices must be initialized prior to calling this function. */ int convert_Mat_rm(const Mat_rm * const in, Mat_rm * const out, const Mat_rm_type type) { int i, j; // Resize the output out->num_rows = in->num_rows; out->num_cols = in->num_cols; out->type = type; if (resize_Mat_rm(out)) return SIFT3D_FAILURE; #define CONVERT_TYPE(type_in, type_out) \ SIFT3D_MAT_RM_LOOP_START(in, i, j) \ SIFT3D_MAT_RM_GET(out, i, j, type_out) = (type_out) \ SIFT3D_MAT_RM_GET(in, i, j, type_in); \ SIFT3D_MAT_RM_LOOP_END #define CONVERT_TYPE_OUTER(type_out) \ switch (in->type) { \ case SIFT3D_DOUBLE: \ CONVERT_TYPE(double, type_out) \ break; \ case SIFT3D_FLOAT: \ CONVERT_TYPE(float, type_out) \ break; \ case SIFT3D_INT: \ CONVERT_TYPE(int, type_out) \ break; \ default: \ puts("convert_Mat_rm: unknown type of input matrix \n"); \ return SIFT3D_FAILURE; \ } // Convert to the specified type switch (type) { case SIFT3D_DOUBLE: CONVERT_TYPE_OUTER(double) break; case SIFT3D_FLOAT: CONVERT_TYPE_OUTER(float) break; case SIFT3D_INT: CONVERT_TYPE_OUTER(int) break; default: puts("convert_Mat_rm: unknown destination type \n"); return SIFT3D_FAILURE; } #undef CONVERT_TYPE_OUTER #undef CONVERT_TYPE return SIFT3D_SUCCESS; } /* Shortcut function to initalize a matrix. * * Parameters: * mat - The matrix to be initialized * num_rows - The number of rows * num_cols - The number of columns * type - The data type * set_zero - If true, initializes the elements to zero. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int init_Mat_rm(Mat_rm *const mat, const int num_rows, const int num_cols, const Mat_rm_type type, const int set_zero) { mat->type = type; mat->num_rows = num_rows; mat->num_cols = num_cols; mat->u.data_double = NULL; mat->size = 0; mat->static_mem = SIFT3D_FALSE; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; if (set_zero && zero_Mat_rm(mat)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* As init_Mat_rm, but aliases data memory with pointer p. The flag * mat->static_mem is set, and the matrix does not need to be freed with * cleanup_Mat_rm. But, an error will be thrown if the user attempts to resize * the memory. That is, resize_Mat_rm will only return success if the size of * the matrix does not change. */ int init_Mat_rm_p(Mat_rm *const mat, const void *const p, const int num_rows, const int num_cols, const Mat_rm_type type, const int set_zero) { // Perform normal initialization if (init_Mat_rm(mat, num_rows, num_cols, type, set_zero)) return SIFT3D_FAILURE; // Clean up any existing memory cleanup_Mat_rm(mat); // Alias with provided memory and set the static flag mat->u.data_double = (double *) p; mat->static_mem = SIFT3D_TRUE; // Optionally set to zero if (set_zero && zero_Mat_rm(mat)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Prints the type of mat into the string str. */ void sprint_type_Mat_rm(const Mat_rm * const mat, char *const str) { switch (mat->type) { case SIFT3D_DOUBLE: sprintf(str, "double"); break; case SIFT3D_FLOAT: sprintf(str, "float"); break; case SIFT3D_INT: sprintf(str, "int"); break; default: sprintf(str, ""); } } /* Concatenate two matrices. If dim = 0, concatenates vertically, i.e. * dst = [src1 * src2]. * If dim = 1, concatenates horizontally, i.e * dst = [src1 src2]. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE on failure. */ int concat_Mat_rm(const Mat_rm * const src1, const Mat_rm * const src2, Mat_rm * const dst, const int dim) { int off[2], dst_dims[2]; int i, j; const int dims1[] = {src1->num_rows, src1->num_cols}; const int dims2[] = {src2->num_rows, src2->num_cols}; // Verify inputs if (dims1[1 - dim] != dims2[1 - dim]) { SIFT3D_ERR("concat_Mat_rm: incompatible dimensions: " "left: [%d x %d] right: [%d x %d] dim: %d \n", dims1[0], dims1[1], dims2[0], dims2[1], dim); return SIFT3D_FAILURE; } if (src1->type != src2->type) { char type1[1024], type2[1024]; sprint_type_Mat_rm(src1, type1); sprint_type_Mat_rm(src2, type2); SIFT3D_ERR("concat_Mat_rm: incompatible types: " "left: <%s> right: <%s> \n", type1, type2); return SIFT3D_FAILURE; } // Compute the destination dimensions for (i = 0; i < 2; i++) { dst_dims[i] = dim == i ? dims1[i] + dims2[i] : dims1[i]; } // Resize dst dst->type = src1->type; dst->num_rows = dst_dims[0]; dst->num_cols = dst_dims[1]; if (resize_Mat_rm(dst)) return SIFT3D_FAILURE; // Compute the offsets for (i = 0; i < 2; i++) { off[i] = dim == i ? dims1[i] : 0; } #define COPY_DATA(type) \ /* Copy src1 data */ \ SIFT3D_MAT_RM_LOOP_START(src1, i, j) \ SIFT3D_MAT_RM_GET(dst, i, j, type) = \ SIFT3D_MAT_RM_GET(src1, i, j, type); \ SIFT3D_MAT_RM_LOOP_END \ \ /* Copy src2 data */ \ SIFT3D_MAT_RM_LOOP_START(src2, i, j) \ \ SIFT3D_MAT_RM_GET(dst, i + off[0], j + off[1], type) = \ SIFT3D_MAT_RM_GET(src2, i, j, type); \ \ SIFT3D_MAT_RM_LOOP_END // Copy the data switch (dst->type) { case SIFT3D_DOUBLE: COPY_DATA(double); break; case SIFT3D_FLOAT: COPY_DATA(float); break; case SIFT3D_INT: COPY_DATA(int); break; default: SIFT3D_ERR("concat_Mat_rm: unknown type \n"); return SIFT3D_FAILURE; } #undef COPY_DATA return SIFT3D_SUCCESS; } /* Copies a matrix. dst will be resized. */ int copy_Mat_rm(const Mat_rm * const src, Mat_rm * const dst) { // Resize dst dst->type = src->type; dst->num_rows = src->num_rows; dst->num_cols = src->num_cols; if (resize_Mat_rm(dst)) return SIFT3D_FAILURE; // Copy the data (use memmove because of static mode) memmove(dst->u.data_double, src->u.data_double, src->size); return SIFT3D_SUCCESS; } /* Print a matrix to stdout. The matrix must be initialized. */ int print_Mat_rm(const Mat_rm * const mat) { int i, j; #define PRINT_MAT_RM(type, format) \ SIFT3D_MAT_RM_LOOP_START(mat, i, j) \ printf("%" #format " ", SIFT3D_MAT_RM_GET(mat, i, j, type)); \ SIFT3D_MAT_RM_LOOP_COL_END \ puts("\n"); \ SIFT3D_MAT_RM_LOOP_ROW_END switch (mat->type) { case SIFT3D_DOUBLE: PRINT_MAT_RM(double, f) break; case SIFT3D_FLOAT: PRINT_MAT_RM(float, f) break; case SIFT3D_INT: PRINT_MAT_RM(int, d) break; default: puts("print_Mat_rm: unknown type \n"); return SIFT3D_FAILURE; } #undef PRINT_MAT_RM return SIFT3D_SUCCESS; } /* Re-sizes a matrix. The following fields * must already be initialized: * -num_rows * -num_cols * -type * -u.data_* (NULL for first use, non-null for resize) * * The following fields will be modified: * -size * -u.data_* (Change is not guaranteed) * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int resize_Mat_rm(Mat_rm *const mat) { size_t type_size, total_size; const int num_rows = mat->num_rows; const int num_cols = mat->num_cols; double **const data = &mat->u.data_double; const size_t numel = num_rows * num_cols; const Mat_rm_type type = mat->type; // Get the size of the underyling datatype switch (type) { case SIFT3D_DOUBLE: type_size = sizeof(double); break; case SIFT3D_FLOAT: type_size = sizeof(float); break; case SIFT3D_INT: type_size = sizeof(int); break; default: SIFT3D_ERR("resize_Mat_rm: unknown type! \n"); return SIFT3D_FAILURE; } // Calculate the new size total_size = type_size * numel; // Do nothing if the size has not changed if (total_size == mat->size) return SIFT3D_SUCCESS; mat->size = total_size; // Check for static reallocation if (mat->static_mem) { SIFT3D_ERR("resize_Mat_rm: illegal re-allocation of static matrix \n"); return SIFT3D_FAILURE; } // Reset if the new size is 0 if (total_size == 0) { cleanup_Mat_rm(mat); return init_Mat_rm(mat, num_rows, num_cols, type, SIFT3D_FALSE); } // Re-allocate the memory if ((*data = (double *) SIFT3D_safe_realloc(*data, total_size)) == NULL) { mat->size = 0; return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Set all elements to zero */ int zero_Mat_rm(Mat_rm *const mat) { int i, j; #define SET_ZERO(type) \ SIFT3D_MAT_RM_LOOP_START(mat, i, j) \ SIFT3D_MAT_RM_GET(mat, i, j, type) = (type) 0; \ SIFT3D_MAT_RM_LOOP_END switch (mat->type) { case SIFT3D_DOUBLE: SET_ZERO(double); break; case SIFT3D_FLOAT: SET_ZERO(float); break; case SIFT3D_INT: SET_ZERO(int); break; default: return SIFT3D_FAILURE; } #undef SET_ZERO return SIFT3D_SUCCESS; } /* Set a matrix to identity. * * Parameters: * n: The length of the square matrix. The output will have size [n x n]. * mat: The matrix to be set. */ int identity_Mat_rm(const int n, Mat_rm *const mat) { int i; // Resize the matrix mat->num_rows = mat->num_cols = n; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; // Set to identity zero_Mat_rm(mat); #define SET_IDENTITY(type) \ for (i = 0; i < n; i++) { \ SIFT3D_MAT_RM_GET(mat, i, i, type) = (type) 1; \ } SIFT3D_MAT_RM_TYPE_MACRO(mat, identity_Mat_rm_quit, SET_IDENTITY); #undef SET_IDENTITY return SIFT3D_SUCCESS; identity_Mat_rm_quit: return SIFT3D_FAILURE; } /* De-allocate the memory for a Mat_rm struct, unless it was initialized in * static mode. */ void cleanup_Mat_rm(Mat_rm *mat) { if (mat->u.data_double == NULL) return; if (!mat->static_mem) free(mat->u.data_double); } /* Make a grid with the specified spacing between lines and line width. * Uses the default stride and initializes grid. */ int draw_grid(Image * grid, int nx, int ny, int nz, int spacing, int line_width) { int x, y, z; const double line_half_width = (double)line_width / 2.0; // Verify inputs if (spacing < 2 || line_width < 1 || line_width > spacing) return SIFT3D_FAILURE; if (init_im_with_dims(grid, nx, ny, nz, 1)) return SIFT3D_FAILURE; SIFT3D_IM_LOOP_START(grid, x, y, z) if (x % spacing == 0 || y % spacing == 0 || z % spacing == 0) { int x_draw, y_draw, z_draw, x_start, x_end, y_start, y_end, z_start, z_end; // Draw a line x_start = SIFT3D_MAX(x - line_half_width, 0); y_start = SIFT3D_MAX(y - line_half_width, 0); z_start = SIFT3D_MAX(z - line_half_width, 0); x_end = SIFT3D_MIN(x + line_half_width + 1, nx - 1); y_end = SIFT3D_MIN(y + line_half_width + 1, ny - 1); z_end = SIFT3D_MIN(z + line_half_width + 1, nz - 1); SIFT3D_IM_LOOP_LIMITED_START(grid, x_draw, y_draw, z_draw, x_start, x_end, y_start, y_end, z_start, z_end) if (abs(x_draw - x) < line_half_width && abs(y_draw - y) < line_half_width && abs(z_draw - z) < line_half_width) SIFT3D_IM_GET_VOX(grid, x_draw, y_draw, z_draw, 0) = 1.0f; SIFT3D_IM_LOOP_END} SIFT3D_IM_LOOP_END return SIFT3D_SUCCESS; } /* Draw points in in image*/ int draw_points(const Mat_rm * const in, const int *const dims, int radius, Image * const out) { Mat_rm in_i; int i, x, y, z; // Initialize intermediates if (init_Mat_rm(&in_i, 0, 0, SIFT3D_INT, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Resize the output memcpy(SIFT3D_IM_GET_DIMS(out), dims, IM_NDIMS * sizeof(int)); out->nc = 1; im_default_stride(out); if (im_resize(out)) goto draw_points_quit; im_zero(out); // Convert the input to integer if (convert_Mat_rm(in, &in_i, SIFT3D_INT)) goto draw_points_quit; for (i = 0; i < in->num_rows; i++) { const int cx = SIFT3D_MAT_RM_GET(&in_i, i, 0, int); const int cy = SIFT3D_MAT_RM_GET(&in_i, i, 1, int); const int cz = SIFT3D_MAT_RM_GET(&in_i, i, 2, int); const int x_start = SIFT3D_MAX(cx - radius, 0); const int y_start = SIFT3D_MAX(cy - radius, 0); const int z_start = SIFT3D_MAX(cz - radius, 0); const int x_end = SIFT3D_MIN(cx + radius, dims[0] - 1); const int y_end = SIFT3D_MIN(cy + radius, dims[1] - 1); const int z_end = SIFT3D_MIN(cz + radius, dims[2] - 1); // Draw the point SIFT3D_IM_LOOP_LIMITED_START(out, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end) SIFT3D_IM_GET_VOX(out, x, y, z, 0) = 1.0f; SIFT3D_IM_LOOP_END} // Clean up cleanup_Mat_rm(&in_i); return SIFT3D_SUCCESS; draw_points_quit: cleanup_Mat_rm(&in_i); return SIFT3D_FAILURE; } /* Draw lines between two sets of points. * TODO currently only does XY plane. Add support for other planes */ int draw_lines(const Mat_rm * const points1, const Mat_rm * const points2, const int *const dims, Image * const out) { Mat_rm points1_d, points2_d; double xd; int i, y; // Parameters const double line_step = 0.1; // Verify inputs if (points1->num_rows != points2->num_rows || points1->num_cols != points2->num_cols || points1->num_cols != IM_NDIMS) { puts("draw_lines: invalid points dimensions \n"); return SIFT3D_FAILURE; } // Initialize intermediates if (init_Mat_rm(&points1_d, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&points2_d, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Resize the output image memcpy(SIFT3D_IM_GET_DIMS(out), dims, IM_NDIMS * sizeof(int)); out->nc = 1; im_default_stride(out); if (im_resize(out)) goto draw_lines_quit; im_zero(out); // Convert the inputs to double if (convert_Mat_rm(points1, &points1_d, SIFT3D_DOUBLE) || convert_Mat_rm(points2, &points2_d, SIFT3D_DOUBLE)) goto draw_lines_quit; for (i = 0; i < points1->num_rows; i++) { const double p1x = SIFT3D_MAT_RM_GET(&points1_d, i, 0, double); const double p2x = SIFT3D_MAT_RM_GET(&points2_d, i, 0, double); const double p1y = SIFT3D_MAT_RM_GET(&points1_d, i, 1, double); const double p2y = SIFT3D_MAT_RM_GET(&points2_d, i, 1, double); const double p1z = SIFT3D_MAT_RM_GET(&points1_d, i, 2, double); const double p2z = SIFT3D_MAT_RM_GET(&points2_d, i, 2, double); // Check the bounds if (!IM_CONTAINS(out, p1x, p1y, p1z) || !IM_CONTAINS(out, p2x, p2y, p2z)) continue; // Get the bounds of the line const double x_start = SIFT3D_MIN(p1x, p2x) + 0.5; const double x_end = SIFT3D_MAX(p1x, p2x) + 0.5; const int zi = (int)p1z; // Check if the line is vertical const int vert = fabs(x_start - x_end) < 1.0; // Draw the line if (vert) { const int xi = (int)x_start; const int y_start = (int)SIFT3D_MIN(p1y, p2y); const int y_end = (int)SIFT3D_MAX(p1y, p2y); for (y = y_start; y <= y_end; y++) { SIFT3D_IM_GET_VOX(out, xi, y, zi, 0) = 1.0f; } } else { // Get the line parameters const double y_slope = p1x < p2x ? (p2y - p1y) / (p2x - p1x) : (p1y - p2y) / (p1x - p2x); const double b = p1y + 0.5 - (p1x + 0.5) * y_slope; for (xd = x_start; xd <= x_end; xd += line_step) { const double yd = y_slope * xd + b; const int xi = (int)xd; const int yi = (int)yd; if (yi < 0 || yi > dims[1] - 1) continue; SIFT3D_IM_GET_VOX(out, xi, yi, zi, 0) = 1.0f; } } } // Clean up cleanup_Mat_rm(&points1_d); cleanup_Mat_rm(&points2_d); return SIFT3D_SUCCESS; draw_lines_quit: cleanup_Mat_rm(&points1_d); cleanup_Mat_rm(&points2_d); return SIFT3D_FAILURE; } /* Detect the format of the supplied file name. */ im_format im_get_format(const char *path) { struct stat st; const char *ext; // Check if the file exists and is a directory if (stat(path, &st) == 0) { if (S_ISDIR(st.st_mode)) return DIRECTORY; } // If not a directory, get the file extension ext = get_file_ext(path); // Check the known types if (!strcmp(ext, ext_analyze) || !strcmp(ext, ext_gz) || !strcmp(ext, ext_nii)) return NIFTI; if (!strcmp(ext, ext_dcm)) return DICOM; if (!strcmp(ext, ext_dir)) return DIRECTORY; // The type was not recognized return UNKNOWN; } /* Read an image from a file. The file extension must match one of the * supported formats. * * Supported formats: * - Analyze (.img, .img.gz) * - DICOM (.dcm) * - Directory of DICOM files * - NIFTI-1 (.nii, .nii.gz) * * Return values: * -SIFT3D_SUCCESS - Image successfully read * -SIFT3D_FILE_DOES_NOT_EXIST - The file does not exist * -SIFT3D_UNSUPPORTED_FILE_TYPE - The file type is not supported * -SIFT3D_WRAPPER_NOT_COMPILED - The file type is supported, but the wrapper * library was not compiled. * -SIFT3D_UNEVEN_SPACING - The image slices are unevenly spaced. * -SIFT3D_INCONSISTENT_AXES - The image slices have inconsistent axes. * -SIFT3D_DUPLICATE_SLICES - There are multiple slices in the same location. * -SIFT3D_FAILURE - Other error */ int im_read(const char *path, Image *const im) { struct stat st; int ret; // Ensure the file exists if (stat(path, &st) != 0) { SIFT3D_ERR("im_read: failed to find file %s \n", path); return SIFT3D_FILE_DOES_NOT_EXIST; } // Get the file format and write the file switch (im_get_format(path)) { case ANALYZE: case NIFTI: ret = read_nii(path, im); break; case DICOM: ret = read_dcm(path, im); break; case DIRECTORY: ret = read_dcm_dir(path, im); break; case FILE_ERROR: ret = SIFT3D_FAILURE; break; case UNKNOWN: default: SIFT3D_ERR("im_read: unrecognized file extension " "from file %s \n", path); ret = SIFT3D_UNSUPPORTED_FILE_TYPE; } return ret; } /* Write an image to a file. * * Supported formats: * -DICOM (.dcm) * -Directory of DICOM files * -NIFTI (.nii, .nii.gz) * * Return values: * -SIFT3D_SUCCESS - Successfully wrote the image * -SIFT3D_UNSUPPORTED_FILE_TYPE - Cannot write this file type * -SIFT3D_FAILURE - Other error */ int im_write(const char *path, const Image *const im) { // Create the path if (mkpath(path, out_mode)) return SIFT3D_FAILURE; // Get the file format switch (im_get_format(path)) { case ANALYZE: case NIFTI: return write_nii(path, im); case DICOM: return write_dcm(path, im, NULL, -1.0f); case DIRECTORY: // Create the directory if (do_mkdir(path, out_mode)) { SIFT3D_ERR("im_write: failed to create directory " "%s \n", path); return SIFT3D_FAILURE; } return write_dcm_dir(path, im, NULL); case UNKNOWN: default: // Otherwise, the file extension was not found SIFT3D_ERR("im_write: unrecognized file extension " "from file %s \n", path); return SIFT3D_UNSUPPORTED_FILE_TYPE; } // Unreachable code return SIFT3D_FAILURE; } /* Separate the file name component from its path */ static const char *get_file_name(const char *path) { const char *name; // Get the last file separator name = strrchr(path, SIFT3D_FILE_SEP); return name == NULL ? path : name; } /* Get the extension of a file name */ static const char *get_file_ext(const char *name) { const char *dot; // Get the file name component name = get_file_name(name); // Get the last dot dot = strrchr(name, '.'); return dot == NULL || dot == name ? "" : dot + 1; } /* Get the parent directory of a file. The returned string must later be * freed. */ char *im_get_parent_dir(const char *path) { ptrdiff_t file_pos; char *dirName; // Duplicate the path so we can edit it dirName = strndup(path, FILENAME_MAX); // Subtract away the file name file_pos = get_file_name(path) - path; if (file_pos > 0) dirName[file_pos] = '\0'; return dirName; } /* Write a matrix to a .csv or .csv.gz file. */ int write_Mat_rm(const char *path, const Mat_rm * const mat) { FILE *file; gzFile gz; const char *ext; int i, j, compress; const char *mode = "w"; // Validate and create the output directory if (mkpath(path, out_mode)) return SIFT3D_FAILURE; // Get the file extension ext = get_file_ext(path); // Check if we need to compress the file compress = strcmp(ext, ext_gz) == 0; // Open the file if (compress) { if ((gz = gzopen(path, mode)) == Z_NULL) return SIFT3D_FAILURE; } else { if ((file = fopen(path, mode)) == NULL) return SIFT3D_FAILURE; } #define WRITE_MAT(mat, format, type) \ SIFT3D_MAT_RM_LOOP_START(mat, i, j) \ const char delim = j < mat->num_cols - 1 ? ',' : '\n'; \ if (compress) { \ gzprintf(gz, format, SIFT3D_MAT_RM_GET(mat, i, j, \ type)); \ gzputc(gz, delim); \ } else { \ fprintf(file, format, SIFT3D_MAT_RM_GET(mat, i, j, \ type)); \ fputc(delim, file); \ } \ SIFT3D_MAT_RM_LOOP_END // Write the matrix switch (mat->type) { case SIFT3D_DOUBLE: WRITE_MAT(mat, "%f", double); break; case SIFT3D_FLOAT: WRITE_MAT(mat, "%f", float); break; case SIFT3D_INT: WRITE_MAT(mat, "%d", int); break; default: goto write_mat_quit; } #undef WRITE_MAT // Check for errors and finish writing the matrix if (compress) { if (gzclose(gz) != Z_OK) goto write_mat_quit; } else { if (ferror(file)) goto write_mat_quit; fclose(file); } return SIFT3D_SUCCESS; write_mat_quit: if (compress) { gzclose(gz); } else { fclose(file); } return SIFT3D_FAILURE; } /* Shortcut to initialize an image for first-time use. * Allocates memory, and assumes the default stride. This * function calls init_im and initializes all values to 0. */ int init_im_with_dims(Image *const im, const int nx, const int ny, const int nz, const int nc) { init_im(im); im->nx = nx; im->ny = ny; im->nz = nz; im->nc = nc; im_default_stride(im); if (im_resize(im)) return SIFT3D_FAILURE; im_zero(im); return SIFT3D_SUCCESS; } /* Calculate the strides of an image object in the default * manner. The following parameters must be initialized: * -nx * -ny * -nz * -nc * If a dimension is not used, its size should be set * to 1. */ void im_default_stride(Image *const im) { size_t prod; int i; prod = (size_t) im->nc; SIFT3D_IM_GET_STRIDES(im)[0] = prod; for (i = 1; i < IM_NDIMS; i++) { prod *= SIFT3D_IM_GET_DIMS(im)[i - 1]; SIFT3D_IM_GET_STRIDES(im)[i] = prod; } } /* Pads an image to a new size. Prior to calling this function, initialize * pad with all dimensions and strides, as in im_resize. Other metadata, * such as units, will be copied from im to pad. */ int im_pad(const Image * const im, Image * const pad) { int x, y, z, c; const int pad_x_end = pad->nx - 1; const int pad_y_end = pad->ny - 1; const int pad_z_end = pad->nz - 1; const int data_x_end = SIFT3D_MIN(im->nx - 1, pad_x_end); const int data_y_end = SIFT3D_MIN(im->ny - 1, pad_y_end); const int data_z_end = SIFT3D_MIN(im->nz - 1, pad_z_end); // Copy relevant metadata, omitting dimensions and strides memcpy(SIFT3D_IM_GET_UNITS(pad), SIFT3D_IM_GET_UNITS(im), IM_NDIMS * sizeof(double)); // Resize the output if (im_resize(pad)) return SIFT3D_FAILURE; // Copy the image SIFT3D_IM_LOOP_LIMITED_START_C(im, x, y, z, c, 0, data_x_end, 0, data_y_end, 0, data_z_end) SIFT3D_IM_GET_VOX(pad, x, y, z, c) = SIFT3D_IM_GET_VOX(im, x, y, z, c); SIFT3D_IM_LOOP_END_C // Pad the remaining data with zeros SIFT3D_IM_LOOP_LIMITED_START_C(im, x, y, z, c, data_x_end, pad_x_end, data_y_end, pad_y_end, data_z_end, pad_z_end) SIFT3D_IM_GET_VOX(pad, x, y, z, c) = 0.0f; SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Resize an image according to the current nx, ny, * and nz. Does not modify scale space information or * strides. Prior to calling this function, use init_im(im) * and initialize the following fields: * -nx * -ny * -nz * -nc * -xs (can be set by im_default_stride(im)) * -ys (can be set by im_default_stride(im)) * -zs (can be set by im_default_stride(im)) * * All of this initialization can also be done with * init_im_with_dims(), which calls this function. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int im_resize(Image *const im) { int i; //FIXME: This will not work for strange strides const size_t size = im->nx * im->ny * im->nz * im->nc; // Verify inputs for (i = 0; i < IM_NDIMS; i++) { const int dim = SIFT3D_IM_GET_DIMS(im)[i]; if (dim > 0) continue; SIFT3D_ERR("im_resize: invalid dimension %d: %d \n", i, dim); return SIFT3D_FAILURE; } if (im->nc < 1) { SIFT3D_ERR("im_resize: invalid number of channels: %d \n", im->nc); return SIFT3D_FAILURE; } // Do nothing if the size has not changed if (im->size == size) return SIFT3D_SUCCESS; im->size = size; // Allocate new memory im->data = SIFT3D_safe_realloc(im->data, size * sizeof(float)); #ifdef SIFT3D_USE_OPENCL { cl_int err; int initialized; if (cl_data.valid) { initialized = (im->data != NULL); // Destroy the old image if (initialized && im->cl_valid) clReleaseMemObject(im->cl_image); // Init an OpenCL image if (im->nz > 0) { im->cl_image = clCreateImage2D(cl_data.context, cl_data. mem_flags, &cl_data. image_format, im->nx, im->ny, im->ys, im->data, &err); } else { im->cl_image = clCreateImage3D(cl_data.context, cl_data. mem_flags, &cl_data. image_format, im->nx, im->ny, im->nz, im->ys, im->zs, im->data, &err); } if (err != CL_SUCCESS) { im->cl_valid = SIFT3D_FALSE; return SIFT3D_FAILURE; } im->cl_valid = SIFT3D_TRUE; } } #endif return size != 0 && im->data == NULL ? SIFT3D_FAILURE : SIFT3D_SUCCESS; } /* Concatenate two images in dimension dim, so that src1 comes before src2. * For example, if dim == 0, the images are horizontally concatenated in x, * so that src1 is on the left and src2 is on the right. Resizes dst. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int im_concat(const Image * const src1, const Image * const src2, const int dim, Image * const dst) { int off[IM_NDIMS], dims_out[IM_NDIMS]; int i, x, y, z, c; const int nc = src1->nc; // Verify inputs for (i = 0; i < IM_NDIMS; i++) { const int src1d = SIFT3D_IM_GET_DIMS(src1)[i]; const int src2d = SIFT3D_IM_GET_DIMS(src2)[i]; if (i == dim) continue; if (src1d != src2d) { SIFT3D_ERR("im_concat: dimension %d must be " "equal in input images. src1: %d src2: %d \n", i, src1d, src2d); return SIFT3D_FAILURE; } } if (src1->nc != src2->nc) { SIFT3D_ERR("im_concat: images must have an equal number " "of channels. src1: %d src2: %d \n", src1->nc, src2->nc); return SIFT3D_FAILURE; } // Get the output dimensions and offsets for (i = 0; i < IM_NDIMS; i++) { const int src1d = SIFT3D_IM_GET_DIMS(src1)[i]; const int src2d = SIFT3D_IM_GET_DIMS(src2)[i]; dims_out[i] = dim == i ? src1d + src2d : src1d; off[i] = dim == i ? src1d : 0; } // Resize dst memcpy(SIFT3D_IM_GET_DIMS(dst), dims_out, IM_NDIMS * sizeof(int)); dst->nc = nc; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Copy the data from src1 SIFT3D_IM_LOOP_START_C(src1, x, y, z, c) SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src1, x, y, z, c); SIFT3D_IM_LOOP_END_C // Copy the data from src2 SIFT3D_IM_LOOP_START_C(src2, x, y, z, c) // Get the destination coordinates with offsets const int x_dst = x + off[0]; const int y_dst = y + off[1]; const int z_dst = z + off[2]; // Copy the data from src2 SIFT3D_IM_GET_VOX(dst, x_dst, y_dst, z_dst, c) = SIFT3D_IM_GET_VOX(src2, x, y, z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Upsample an image by a factor of 2 in each dimension. * This function resizes dst and modifies its units. */ int im_upsample_2x(const Image *const src, Image *const dst) { double units[IM_NDIMS]; int dims[IM_NDIMS]; int i, x, y, z, c, sx, sy, sz; const int nc = src->nc; const int w = 2; const float weight = (float) (1.0 / pow((double) w, IM_NDIMS)); // Resize the output for (i = 0; i < IM_NDIMS; i++) { dims[i] = SIFT3D_IM_GET_DIMS(src)[i] * 2; units[i] = SIFT3D_IM_GET_UNITS(src)[i] / 2.0; } memcpy(SIFT3D_IM_GET_DIMS(dst), dims, IM_NDIMS * sizeof(int)); memcpy(SIFT3D_IM_GET_UNITS(dst), units, IM_NDIMS * sizeof(float)); dst->nc = nc; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // TODO: 3-pass (separable) upsample might be faster // Upsample SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) const int sx_start = x >> 1; const int sy_start = y >> 1; const int sz_start = z >> 1; const int sx_end = sx_start + w - 1; const int sy_end = sy_start + w - 1; const int sz_end = sz_start + w - 1; SIFT3D_IM_GET_VOX(dst, x, y, z, c) = 0; SIFT3D_IM_LOOP_LIMITED_START(dst, sx, sy, sz, sx_start, sx_end, sy_start, sy_end, sz_start, sz_end) SIFT3D_IM_GET_VOX(dst, x, y, z, c) += SIFT3D_IM_GET_VOX(src, sx, sy, sz, c); SIFT3D_IM_LOOP_END SIFT3D_IM_GET_VOX(dst, x, y, z, c) *= weight; SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Downsample an image by a factor of 2 in each dimension. * This function initializes dst with the proper * dimensions, and allocates memory. */ int im_downsample_2x(const Image *const src, Image *const dst) { int x, y, z, c; // Initialize dst dst->nx = (int)floor((double)src->nx / 2.0); dst->ny = (int)floor((double)src->ny / 2.0); dst->nz = (int)floor((double)src->nz / 2.0); dst->nc = src->nc; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Downsample SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) const int src_x = x << 1; const int src_y = y << 1; const int src_z = z << 1; SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src, src_x, src_y, src_z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Same as im_downsample_2x, but with OpenCL acceleration. This function DOES NOT * read the results back dst->data. Use im_read_back for that. */ int im_downsample_2x_cl(Image * src, Image * dst) { #ifdef SIFT3D_USE_OPENCL size_t global_work_size[3]; cl_int err, dim; cl_kernel kernel; // Verify image dimensions if (src->nx % 2 != 0 || src->ny % 2 != 0 || src->nz % 2 != 0) return SIFT3D_FAILURE; // Initialize dst dimensions, resized in im_set_kernel_arg dst->nx = src->nx / 2; dst->ny = src->ny / 2; dst->nz = src->nz / 2; dst->nc = src->nc; im_default_stride(dst); // Do not have a 2D kernel right now assert(src->nz > 0); dim = 3; global_work_size[0] = dst->nx; global_work_size[1] = dst->ny; global_work_size[2] = dst->nz; kernel = cl_data.kernels.downsample_2x_3d; im_set_kernel_arg(kernel, 0, src); im_set_kernel_arg(kernel, 1, dst); err = clEnqueueNDRangeKernel(cl_data.queues[0], kernel, dim, NULL, global_work_size, NULL, 0, NULL, NULL); return (int)err; #else printf ("im_downsample_2x_cl: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Loads the C-accessible data of an image into its OpenCL data. If blocking is set, * block the function until the load is complete. */ int im_load_cl(Image * im, int blocking) { #ifdef SIFT3D_USE_OPENCL const size_t origin[] = { 0, 0, 0 }; const size_t region[] = { im->nx, im->ny, im->nz }; const cl_bool cl_blocking = (blocking) ? CL_TRUE : CL_FALSE; return clEnqueueWriteImage(cl_data.queues[0], im->cl_image, cl_blocking, origin, region, im->ys, im->zs, im->data, 0, NULL, NULL); #else printf("im_load_cl: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Reads the OpenCL data of an image back to its C-accessible data. If blocking is set, * block the function until the read is complete. */ int im_read_back(Image * im, int blocking) { #ifdef SIFT3D_USE_OPENCL const size_t origin[] = { 0, 0, 0 }; const size_t region[] = { im->nx, im->ny, im->nz }; const cl_bool cl_blocking = (blocking) ? CL_TRUE : CL_FALSE; return clEnqueueReadImage(cl_data.queues[0], im->cl_image, cl_blocking, origin, region, im->ys, im->zs, im->data, 0, NULL, NULL); #else printf("im_read_back: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Updates an Image struct's OpenCL data, if neccessary, and sets it as argument n * in the provided kernel. */ int im_set_kernel_arg(cl_kernel kernel, int n, Image * im) { #ifdef SIFT3D_USE_OPENCL cl_int err; if (!im->cl_valid && im_resize(im)) return SIFT3D_FAILURE; err = clSetKernelArg(kernel, n, sizeof(cl_mem), &im->cl_image); check_cl_error(err, "im_set_kernel_arg"); return SIFT3D_SUCCESS; #else printf ("im_set_kernel_arg: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Copy an image's dimensions and stride into another. * This function resizes dst. * * @param src The source image. * @param dst The destination image. * @return Returns SIFT3D_SUCCESS or SIFT3D_FAILURE. */ int im_copy_dims(const Image * const src, Image * dst) { if (src->data == NULL) return SIFT3D_FAILURE; dst->nx = src->nx; dst->ny = src->ny; dst->nz = src->nz; dst->xs = src->xs; dst->ys = src->ys; dst->zs = src->zs; dst->nc = src->nc; dst->ux = src->ux; dst->uy = src->uy; dst->uz = src->uz; return im_resize(dst); } /* Copy an image's data into another. This function * changes the dimensions and stride of dst, * and allocates memory. */ int im_copy_data(const Image * const src, Image * const dst) { int x, y, z, c; // Return if src has no data if (src->data == NULL) return SIFT3D_FAILURE; // Return if src and dst are the same if (dst->data == src->data) return SIFT3D_SUCCESS; // Resize dst if (im_copy_dims(src, dst)) return SIFT3D_FAILURE; // Copy data SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src, x, y, z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Clean up memory for an Image */ void im_free(Image * im) { if (im->data != NULL) free(im->data); } /* Make a deep copy of a single channel of an image. */ int im_channel(const Image * const src, Image * const dst, const unsigned int chan) { int x, y, z; const int c = chan; // Verify inputs if (c >= src->nc) { SIFT3D_ERR("im_channel: invalid channel: %d, image has " "%d channels", c, src->nc); return SIFT3D_FAILURE; } // Resize the output memcpy(SIFT3D_IM_GET_DIMS(dst), SIFT3D_IM_GET_DIMS(src), IM_NDIMS * sizeof(int)); dst->nc = 1; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Copy the channel SIFT3D_IM_LOOP_START(dst, x, y, z) SIFT3D_IM_GET_VOX(dst, x, y, z, 0) = SIFT3D_IM_GET_VOX(src, x, y, z, c); SIFT3D_IM_LOOP_END return SIFT3D_SUCCESS; } /* Find the maximum absolute value of an image */ float im_max_abs(const Image *const im) { float max; int x, y, z, c; max = 0.0f; SIFT3D_IM_LOOP_START_C(im, x, y, z, c) const float samp = fabsf(SIFT3D_IM_GET_VOX(im, x, y, z, c)); max = SIFT3D_MAX(max, samp); SIFT3D_IM_LOOP_END_C return max; } /* Scale an image to the [-1, 1] range, where * the largest absolute value is 1. */ void im_scale(const Image *const im) { int x, y, z, c; // Find the maximum absolute value const float max = im_max_abs(im); if (max == 0.0f) return; // Divide by the max SIFT3D_IM_LOOP_START_C(im, x, y, z, c) SIFT3D_IM_GET_VOX(im, x, y, z, c) /= max; SIFT3D_IM_LOOP_END_C } /* Subtract src2 from src1, saving the result in * dst. * Resizes dst. */ int im_subtract(Image * src1, Image * src2, Image * dst) { int x, y, z, c; // Verify inputs if (src1->nx != src2->nx || src1->ny != src2->ny || src1->nz != src2->nz || src1->nc != src2->nc) return SIFT3D_FAILURE; // Resize the output image if (im_copy_dims(src1, dst)) return SIFT3D_FAILURE; SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src1, x, y, z, c) - SIFT3D_IM_GET_VOX(src2, x, y, z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Zero an image. */ void im_zero(Image * im) { int x, y, z, c; SIFT3D_IM_LOOP_START_C(im, x, y, z, c) SIFT3D_IM_GET_VOX(im, x, y, z, c) = 0.0f; SIFT3D_IM_LOOP_END_C} /* Transform an image according to the inverse of the provided tform. * * Paramters: * tform: The transformation. * src: The input image. * interp: The type of interpolation. * resize: If true, resizes the dst to be the same size as src. Otherwise, * uses the dimensions of dst. * dst: The output image. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int im_inv_transform(const void *const tform, const Image * const src, const interp_type interp, const int resize, Image *const dst) { int x, y, z, c; // Optionally resize the output image if (resize && im_copy_dims(src, dst)) return SIFT3D_FAILURE; #define IMUTIL_RESAMPLE(arg) \ SIFT3D_IM_LOOP_START(dst, x, y, z) \ \ double transx, transy, transz; \ \ apply_tform_xyz(tform, (double)x, (double)y, (double)z, \ &transx, &transy, &transz); \ \ for (c = 0; c < dst->nc; c++) { \ SIFT3D_IM_GET_VOX(dst, x, y, z, c) = resample_ ## arg(src, \ transx, transy, transz, c); \ } \ SIFT3D_IM_LOOP_END // Transform switch (interp) { case LINEAR: IMUTIL_RESAMPLE(linear) break; case LANCZOS2: IMUTIL_RESAMPLE(lanczos2) break; default: SIFT3D_ERR("im_inv_transform: unrecognized " "interpolation type"); return SIFT3D_FAILURE; } #undef RESAMPLE return SIFT3D_SUCCESS; } /* Helper routine for image transformation. Performs trilinear * interpolation, setting out-of-bounds voxels to zero. */ static double resample_linear(const Image * const in, const double x, const double y, const double z, const int c) { // Detect out-of-bounds if (x < 0 || x > in->nx - 1 || y < 0 || y > in->ny - 1 || z < 0 || z > in->nz - 1) return 0.0; int fx = (int)floor(x); int fy = (int)floor(y); int fz = (int)floor(z); int cx = (int)ceil(x); int cy = (int)ceil(y); int cz = (int)ceil(z); double dist_x = x - fx; double dist_y = y - fy; double dist_z = z - fz; double c0 = SIFT3D_IM_GET_VOX(in, fx, fy, fz, c); double c1 = SIFT3D_IM_GET_VOX(in, fx, cy, fz, c); double c2 = SIFT3D_IM_GET_VOX(in, cx, fy, fz, c); double c3 = SIFT3D_IM_GET_VOX(in, cx, cy, fz, c); double c4 = SIFT3D_IM_GET_VOX(in, fx, fy, cz, c); double c5 = SIFT3D_IM_GET_VOX(in, fx, cy, cz, c); double c6 = SIFT3D_IM_GET_VOX(in, cx, fy, cz, c); double c7 = SIFT3D_IM_GET_VOX(in, cx, cy, cz, c); double out = c0 * (1.0 - dist_x) * (1.0 - dist_y) * (1.0 - dist_z) + c1 * (1.0 - dist_x) * dist_y * (1.0 - dist_z) + c2 * dist_x * (1.0 - dist_y) * (1.0 - dist_z) + c3 * dist_x * dist_y * (1.0 - dist_z) + c4 * (1.0 - dist_x) * (1.0 - dist_y) * dist_z + c5 * (1.0 - dist_x) * dist_y * dist_z + c6 * dist_x * (1.0 - dist_y) * dist_z + c7 * dist_x * dist_y * dist_z; return out; } /* Helper routine to resample an image at a point, using the Lanczos kernel */ static double resample_lanczos2(const Image * const im, const double x, const double y, const double z, const int c) { double val; int xs, ys, zs; //TODO: faster separable implementation // Kernel parameter const double a = 2; // Check bounds const double xMin = 0; const double yMin = 0; const double zMin = 0; const double xMax = im->nx - 1; const double yMax = im->ny - 1; const double zMax = im->nz - 1; if (x < xMin || y < yMin || z < zMin || x > xMax || y > yMax || z > zMax) return 0.0; // Window const int x_start = SIFT3D_MAX(floor(x) - a, xMin); const int x_end = SIFT3D_MIN(floor(x) + a, xMax); const int y_start = SIFT3D_MAX(floor(y) - a, yMin); const int y_end = SIFT3D_MIN(floor(y) + a, yMax); const int z_start = SIFT3D_MAX(floor(z) - a, zMin); const int z_end = SIFT3D_MIN(floor(z) + a, zMax); // Iterate through the window val = 0.0; SIFT3D_IM_LOOP_LIMITED_START(in, xs, ys, zs, x_start, x_end, y_start, y_end, z_start, z_end) // Evalutate the kernel const double xw = fabs((double)xs - x) + DBL_EPSILON; const double yw = fabs((double)ys - y) + DBL_EPSILON; const double zw = fabs((double)zs - z) + DBL_EPSILON; const double kernel = lanczos(xw, a) * lanczos(yw, a) * lanczos(zw, a); // Accumulate val += kernel * SIFT3D_IM_GET_VOX(im, xs, ys, zs, c); SIFT3D_IM_LOOP_END return val; } /* Lanczos kernel function */ static double lanczos(double x, double a) { const double pi_x = M_PI * x; return a * sin(pi_x) * sin(pi_x / a) / (pi_x * pi_x); } /* Resample an image to different units. * * Parameters: * src: The input image. * units: The new units. * interp: The type of interpolation to use. * dst: The output image. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int im_resample(const Image *const src, const double *const units, const interp_type interp, Image *const dst) { Affine aff; Mat_rm A; double factors[IM_NDIMS]; int i; // Initialize intermediates if (init_Mat_rm(&A, IM_NDIMS, IM_NDIMS + 1, SIFT3D_DOUBLE, SIFT3D_TRUE) || init_Affine(&aff, IM_NDIMS)) return SIFT3D_FAILURE; // Compute the scaling factors for (i = 0; i < IM_NDIMS; i++) { factors[i] = SIFT3D_IM_GET_UNITS(src)[i] / units[i]; } // Set the transformation matrix for (i = 0; i < IM_NDIMS; i++) { SIFT3D_MAT_RM_GET(&A, i, i, double) = 1.0 / factors[i]; } if (Affine_set_mat(&A, &aff)) goto im_resample_quit; // Set the output dimensions dst->nc = src->nc; for (i = 0; i < IM_NDIMS; i++) { SIFT3D_IM_GET_DIMS(dst)[i] = (int) ceil((double) SIFT3D_IM_GET_DIMS(src)[i] * factors[i]); } im_default_stride(dst); if (im_resize(dst)) goto im_resample_quit; // Apply the transformation if (im_inv_transform(&aff, src, interp, SIFT3D_FALSE, dst)) goto im_resample_quit; // Set the new output units memcpy(SIFT3D_IM_GET_UNITS(dst), units, IM_NDIMS * sizeof(double)); // Clean up cleanup_tform(&aff); cleanup_Mat_rm(&A); return SIFT3D_SUCCESS; im_resample_quit: cleanup_tform(&aff); cleanup_Mat_rm(&A); return SIFT3D_FAILURE; } /* Horizontally convolves a separable filter with an image, * on CPU. Currently only works in 3D. * * This function chooses among the best variant of convolve_sep* based on * compilation options and filter parameters. * * Parameters: * src - input image (initialized) * dst - output image (initialized) int x, y, z; * f - filter to be applied * dim - dimension in which to convolve * unit - the spacing of the filter coefficients */ static int convolve_sep(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit) { #ifdef SIFT3D_USE_OPENCL return convolve_sep_cl(src, dst, f, dim, unit); #else return f->symmetric ? convolve_sep_sym(src, dst, f, dim, unit) : convolve_sep_gen(src, dst, f, dim, unit); #endif } /* Convolve_sep for general filters */ static int convolve_sep_gen(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit) { register int x, y, z, c, d; register const int half_width = f->width / 2; register const int nx = src->nx; register const int ny = src->ny; register const int nz = src->nz; register const float conv_eps = 0.1f; register const int dim_end = SIFT3D_IM_GET_DIMS(src)[dim] - 1; register const float unit_factor = unit / SIFT3D_IM_GET_UNITS(src)[dim]; register const int unit_half_width = (int) ceilf(half_width * unit_factor); int start[] = {0, 0, 0}; int end[] = {nx - 1, ny - 1, nz - 1}; // Compute starting and ending points for the convolution dimension start[dim] += unit_half_width; end[dim] -= unit_half_width + 1; //TODO: Convert this to convolve_x, which only convolves in x, // then make a wrapper to restride, transpose, convolve x, and transpose // back // Resize the output, with the default stride if (im_copy_dims(src, dst)) return SIFT3D_FAILURE; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Initialize the output to zeros im_zero(dst); #define SAMP_AND_ACC(src, dst, tap, coords, c) \ { \ float frac; \ \ const int idx_lo[] = {(coords)[0], (coords)[1], (coords)[2]}; \ int idx_hi[] = {idx_lo[0], idx_lo[1], idx_lo[2]}; \ \ /* Convert the physical coordinates to integer indices*/ \ idx_hi[dim] += 1; \ frac = (coords)[dim] - (float) idx_lo[dim]; \ \ /* Sample with linear interpolation */ \ SIFT3D_IM_GET_VOX(dst, x, y, z, c) += (tap) * \ ((1.0f - frac) * \ SIFT3D_IM_GET_VOX(src, idx_lo[0], idx_lo[1], idx_lo[2], c) + \ frac * \ SIFT3D_IM_GET_VOX(src, idx_hi[0], idx_hi[1], idx_hi[2], c)); \ } // First pass: process the interior #pragma omp parallel for private(x) private(y) private(c) SIFT3D_IM_LOOP_LIMITED_START_C(dst, x, y, z, c, start[0], end[0], start[1], end[1], start[2], end[2]) float coords[] = { x, y, z }; for (d = -half_width; d <= half_width; d++) { const float tap = f->kernel[d + half_width]; const float step = d * unit_factor; // Adjust the sampling coordinates coords[dim] -= step; // Sample SAMP_AND_ACC(src, dst, tap, coords, c); // Reset the sampling coordinates coords[dim] += step; } SIFT3D_IM_LOOP_END_C // Second pass: process the boundaries #pragma omp parallel for private(x) private(y) private(c) SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) const int i_coords[] = { x, y, z }; // Skip pixels we have already processed if (i_coords[dim] >= start[dim] && i_coords[dim] <= end[dim]) continue; // Process the boundary pixel for (d = -half_width; d <= half_width; d++) { float coords[] = { x, y, z }; const float tap = f->kernel[d + half_width]; const float step = d * unit_factor; // Adjust the sampling coordinates coords[dim] -= step; // Mirror coordinates if ((int) coords[dim] < 0) { coords[dim] = -coords[dim]; assert((int) coords[dim] >= 0); } else if ((int) coords[dim] >= dim_end) { coords[dim] = 2.0f * dim_end - coords[dim] - conv_eps; assert((int) coords[dim] < dim_end); } // Sample SAMP_AND_ACC(src, dst, tap, coords, c); } SIFT3D_IM_LOOP_END_C #undef SAMP_AND_ACC return SIFT3D_SUCCESS; } /* Same as convolve_sep, but with OpenCL acceleration. This does NOT * read back the results to C-accessible data. Use im_read_back for that. */ SIFT3D_IGNORE_UNUSED static int convolve_sep_cl(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit) { #ifdef SIFT3D_USE_OPENCL cl_kernel kernel; cl_int dx, dy, dz, err; const size_t global_work_size[] = { src->nx, src->ny, src->nz }; // Resize the output, with the default stride if (im_copy_dims(src, dst)) return SIFT3D_FAILURE; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Do not have a 2D kernel right now if (dim != 3) { printf("convolve_sep_cl: unsupported dimension: %d \n", dim); return SIFT3D_FAILURE} // Form the dimension offsets dx = dy = dz = 0; switch (dim) { case 0: dx = 1; break; case 1: dy = 1; break; case 2: dz = 1; break; default: return SIFT3D_FAILURE; } kernel = f->cl_apply_unrolled; im_set_kernel_arg(kernel, 0, src); im_set_kernel_arg(kernel, 1, dst); err = clSetKernelArg(kernel, 2, sizeof(cl_int), &dx); err |= clSetKernelArg(kernel, 3, sizeof(cl_int), &dy); err |= clSetKernelArg(kernel, 4, sizeof(cl_int), &dz); check_cl_error(err, "convolve_sep_cl: set kernel arg"); err = clEnqueueNDRangeKernel(cl_data.queues[0], kernel, dim, NULL, global_work_size, NULL, 0, NULL, NULL); return (int)err; #else printf("colvolve_sep_cl: This version was not compiled with OpenCL!\n"); return SIFT3D_FAILURE; #endif } /* Convolve_sep for symmetric filters. */ static int convolve_sep_sym(const Image * const src, Image * const dst, const Sep_FIR_filter * const f, const int dim, const double unit) { // TODO: Symmetry-specific function return convolve_sep_gen(src, dst, f, dim, unit); } /* Permute the dimensions of an image. * * Arguments: * src - input image (initialized) * dim1 - input permutation dimension (x = 0, y = 1, z = 2) * dim2 - output permutation dimension (x = 0, y = 1, z = 2) * dst - output image (initialized) * * example: * im_permute(src, dst, 0, 1) -- permute x with y in src * and save to dst */ int im_permute(const Image * const src, const int dim1, const int dim2, Image * const dst) { register int x, y, z, c; // Verify inputs if (dim1 < 0 || dim2 < 0 || dim1 > 3 || dim2 > 3) { printf("im_permute: invalid dimensions: dim1 %d dim2 %d \n", dim1, dim2); return SIFT3D_FAILURE; } // Check for the trivial case if (dim1 == dim2) { return im_copy_data(src, dst); } // Permute the units memcpy(SIFT3D_IM_GET_UNITS(dst), SIFT3D_IM_GET_UNITS(src), IM_NDIMS * sizeof(double)); SIFT3D_IM_GET_UNITS(dst)[dim1] = SIFT3D_IM_GET_UNITS(src)[dim2]; SIFT3D_IM_GET_UNITS(dst)[dim2] = SIFT3D_IM_GET_UNITS(src)[dim1]; // Resize the output memcpy(SIFT3D_IM_GET_DIMS(dst), SIFT3D_IM_GET_DIMS(src), IM_NDIMS * sizeof(int)); SIFT3D_IM_GET_DIMS(dst)[dim1] = SIFT3D_IM_GET_DIMS(src)[dim2]; SIFT3D_IM_GET_DIMS(dst)[dim2] = SIFT3D_IM_GET_DIMS(src)[dim1]; dst->nc = src->nc; im_default_stride(dst); if (im_resize(dst)) return SIFT3D_FAILURE; // Transpose the data SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) int src_coords[] = {x, y, z}; int temp; // Permute the coordinates temp = src_coords[dim1]; src_coords[dim1] = src_coords[dim2]; src_coords[dim2] = temp; // Copy the datum SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src, src_coords[0], src_coords[1], src_coords[2], c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Change an image's stride, preserving its data. * * Parameters: * -src: The source image. Must be initialized * -strides: an array of length IM_NDIMS specifying the new strides * -dst: The destination image. Must be initialized * * Return: SIFT3D_SUCCESS (0) on success, nonzero otherwise. */ int im_restride(const Image * const src, const size_t *const strides, Image * const dst) { int x, y, z, c; // Resize the output memcpy(SIFT3D_IM_GET_DIMS(dst), SIFT3D_IM_GET_DIMS(src), IM_NDIMS * sizeof(int)); memcpy(SIFT3D_IM_GET_STRIDES(dst), strides, IM_NDIMS * sizeof(size_t)); dst->nc = src->nc; if (im_resize(dst)) return SIFT3D_FAILURE; // Copy the data SIFT3D_IM_LOOP_START_C(dst, x, y, z, c) SIFT3D_IM_GET_VOX(dst, x, y, z, c) = SIFT3D_IM_GET_VOX(src, x, y, z, c); SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Initializes a tform to ensure memory safety. * Either this or the type-specific version must be called prior to using * a tform. */ int init_tform(void *const tform, const tform_type type) { switch (type) { case TPS: puts("init_tform: TPS not yet implemented \n"); return SIFT3D_FAILURE; case AFFINE: if (init_Affine((Affine *) tform, IM_NDIMS)) return SIFT3D_FAILURE; break; default: puts("init_tform: unrecognized type \n"); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Initialize an Affine struct. This initializes * all fields, and allocates memory for the inner * matrix, initializing it to zero. */ int init_Affine(Affine * const affine, const int dim) { // Verify inputs if (dim < 2) return SIFT3D_FAILURE; // Initialize the type affine->tform.type = AFFINE; // Initialize the vtable affine->tform.vtable = &Affine_vtable; // Initialize the matrix if (init_Mat_rm(&affine->A, dim, dim + 1, SIFT3D_DOUBLE, SIFT3D_TRUE)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Deep copy of a tform. Both src and dst must be initialized. */ int copy_tform(const void *const src, void *const dst) { return TFORM_GET_VTABLE(src)->copy(src, dst); } /* Deep copy of one Affine to another. Both must be initialized. */ static int copy_Affine(const void *const src, void *const dst) { const Affine *const srcAff = src; Affine *const dstAff = dst; return Affine_set_mat(&srcAff->A, dstAff); } /* Deep copy of one TPS to another. Both must be initialized. */ static int copy_Tps(const void *const src, void *const dst) { SIFT3D_ERR("copy_Tps has not yet been implemented!"); return SIFT3D_FAILURE; } /* Set an Affine transform to the given matrix. * mat is copied. mat must be an n x (n + 1) matrix, where * n is the dimensionality of the transformation. */ int Affine_set_mat(const Mat_rm * const mat, Affine * const affine) { // Verify inputs if (mat->num_cols != mat->num_rows + 1 || mat->num_rows < 2) return SIFT3D_FAILURE; return convert_Mat_rm(mat, &affine->A, SIFT3D_DOUBLE); } /* Apply an arbitrary transformation to an [x, y, z] triple. */ void apply_tform_xyz(const void *const tform, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out) { TFORM_GET_VTABLE(tform)->apply_xyz(tform, x_in, y_in, z_in, x_out, y_out, z_out); } /* Apply an Affine transformation to an [x, y, z] triple. */ static void apply_Affine_xyz(const void *const affine, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out) { const Affine *const aff = affine; const Mat_rm *const A = &aff->A; assert(AFFINE_GET_DIM(aff) == 3); *x_out = SIFT3D_MAT_RM_GET(A, 0, 0, double) * x_in + SIFT3D_MAT_RM_GET(A, 0, 1, double) * y_in + SIFT3D_MAT_RM_GET(A, 0, 2, double) * z_in + SIFT3D_MAT_RM_GET(A, 0, 3, double); *y_out = SIFT3D_MAT_RM_GET(A, 1, 0, double) * x_in + SIFT3D_MAT_RM_GET(A, 1, 1, double) * y_in + SIFT3D_MAT_RM_GET(A, 1, 2, double) * z_in + SIFT3D_MAT_RM_GET(A, 1, 3, double); *z_out = SIFT3D_MAT_RM_GET(A, 2, 0, double) * x_in + SIFT3D_MAT_RM_GET(A, 2, 1, double) * y_in + SIFT3D_MAT_RM_GET(A, 2, 2, double) * z_in + SIFT3D_MAT_RM_GET(A, 2, 3, double); } /* Apply a thin-plate spline transformation to an [x, y, z] triple. */ static void apply_Tps_xyz(const void *const tps, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out) { const Tps *const t = tps; const Mat_rm *const params = &t->params; const Mat_rm *const kp_src = &t->kp_src; assert(t->dim == 3); int n; int ctrl_pts = kp_src->num_rows; //number of control points double x_c, y_c, z_c, r_sq, U; double temp_x = 0.0, temp_y = 0.0, temp_z = 0.0; for (n = 0; n < ctrl_pts; n++) { x_c = SIFT3D_MAT_RM_GET(kp_src, n, 0, double); y_c = SIFT3D_MAT_RM_GET(kp_src, n, 1, double); z_c = SIFT3D_MAT_RM_GET(kp_src, n, 2, double); r_sq = (x_in - x_c) * (x_in - x_c) + (y_in - y_c) * (y_in - y_c) + (z_in - z_c) * (z_in - z_c); if (r_sq == 0) { U = 0.0; } else { U = r_sq * log(r_sq); } temp_x += U * SIFT3D_MAT_RM_GET(params, 0, n, double); temp_y += U * SIFT3D_MAT_RM_GET(params, 1, n, double); temp_z += U * SIFT3D_MAT_RM_GET(params, 2, n, double); } temp_x += SIFT3D_MAT_RM_GET(params, 0, ctrl_pts, double); temp_x += SIFT3D_MAT_RM_GET(params, 0, ctrl_pts + 1, double) * x_in; temp_x += SIFT3D_MAT_RM_GET(params, 0, ctrl_pts + 2, double) * y_in; temp_x += SIFT3D_MAT_RM_GET(params, 0, ctrl_pts + 3, double) * z_in; temp_y += SIFT3D_MAT_RM_GET(params, 1, ctrl_pts, double); temp_y += SIFT3D_MAT_RM_GET(params, 1, ctrl_pts + 1, double) * x_in; temp_y += SIFT3D_MAT_RM_GET(params, 1, ctrl_pts + 2, double) * y_in; temp_y += SIFT3D_MAT_RM_GET(params, 1, ctrl_pts + 3, double) * z_in; temp_z += SIFT3D_MAT_RM_GET(params, 2, ctrl_pts, double); temp_z += SIFT3D_MAT_RM_GET(params, 2, ctrl_pts + 1, double) * x_in; temp_z += SIFT3D_MAT_RM_GET(params, 2, ctrl_pts + 2, double) * y_in; temp_z += SIFT3D_MAT_RM_GET(params, 2, ctrl_pts + 3, double) * z_in; //Save results x_out[0] = temp_x; y_out[0] = temp_y; z_out[0] = temp_z; } /* Apply an arbitrary transform to a matrix. See apply_Affine_Mat_rm for * matrix formats. */ int apply_tform_Mat_rm(const void *const tform, const Mat_rm * const mat_in, Mat_rm * const mat_out) { return TFORM_GET_VTABLE(tform)->apply_Mat_rm(tform, mat_in, mat_out); } /* Apply a spline transformation to a matrix, * by multiplication. See apply_Affine_Mat_rm for format of input matrices * * All matrices must be initialized with init_Mat_rm prior to use. For 3D!*/ static int apply_Tps_Mat_rm(const void *const tps, const Mat_rm * const mat_in, Mat_rm * const mat_out) { const Tps *const t = tps; //Spline transformation matrix is dim * [number of chosen points+dim+1] //sp_src is [number of chosen points] * dim const Mat_rm *const params = &(t->params); const Mat_rm *const kp_src = &(t->kp_src); int num_pts = mat_in->num_cols; //number of points to be transformed int ctrl_pts = kp_src->num_rows; //number of control points int m, n, q; double temp, x, y, z, r_sq, x_c, y_c, z_c; double U[ctrl_pts]; //for each point for (q = 0; q < num_pts; q++) { //extract the coordinates x = SIFT3D_MAT_RM_GET(mat_in, 0, q, double); y = SIFT3D_MAT_RM_GET(mat_in, 1, q, double); z = SIFT3D_MAT_RM_GET(mat_in, 2, q, double); //Calculate U function for each control point for (n = 0; n < ctrl_pts; n++) { x_c = SIFT3D_MAT_RM_GET(kp_src, n, 0, double); y_c = SIFT3D_MAT_RM_GET(kp_src, n, 1, double); z_c = SIFT3D_MAT_RM_GET(kp_src, n, 2, double); r_sq = (x - x_c) * (x - x_c) + (y - y_c) * (y - y_c) + (z - z_c) * (z - z_c); if (r_sq == 0) { U[n] = 0.0; } else { U[n] = r_sq * log(r_sq); } } //for each dimension for (m = 0; m < 3; m++) { //For 3D! temp = 0.0; for (n = 0; n < ctrl_pts; n++) { temp += U[n] * SIFT3D_MAT_RM_GET(params, m, n, double); } temp += SIFT3D_MAT_RM_GET(params, m, ctrl_pts, double); temp += SIFT3D_MAT_RM_GET(params, m, ctrl_pts + 1, double) * x; temp += SIFT3D_MAT_RM_GET(params, m, ctrl_pts + 2, double) * y; temp += SIFT3D_MAT_RM_GET(params, m, ctrl_pts + 3, double) * z; //Store results SIFT3D_MAT_RM_GET(mat_out, m, q, double) = temp; } } return SIFT3D_SUCCESS; } /* Get the type of a tform. */ tform_type tform_get_type(const void *const tform) { return ((Affine *) tform)->tform.type; } /* Get the size of a tform. */ size_t tform_get_size(const void *const tform) { return TFORM_GET_VTABLE(tform)->get_size(); } /* Get the size of a type of tform. */ size_t tform_type_get_size(const tform_type type) { switch (type) { case AFFINE: return Affine_vtable.get_size(); case TPS: return Tps_vtable.get_size(); default: SIFT3D_ERR("tform_type_get_size: unrecognized " "type \n"); return 0; } } /* Returns the size of an Affine struct */ static size_t Affine_get_size(void) { return sizeof(Affine); } /* Returns the size of a Tps struct */ static size_t Tps_get_size(void) { return sizeof(Tps); } /* Write a tform to a file. */ int write_tform(const char *path, const void *const tform) { return TFORM_GET_VTABLE(tform)->write(path, tform); } /* Write an affine transformation to a file. */ static int write_Affine(const char *path, const void *const tform) { const Affine *const affine = tform; return write_Mat_rm(path, &affine->A); } /* Write a thin-plate spline transformation to a file. */ static int write_Tps(const char *path, const void *const tform) { SIFT3D_IGNORE_UNUSED const Tps *const tps = tform; SIFT3D_ERR("write_Tps: this function has not yet been implemented."); return SIFT3D_FAILURE; } /* Free the memory associated with a tform */ void cleanup_tform(void *const tform) { TFORM_GET_VTABLE(tform)->cleanup(tform); } /* Free the memory associated with an Affine transformation. */ static void cleanup_Affine(void *const affine) { Affine *const aff = affine; cleanup_Mat_rm(&aff->A); } /* Free the memory assocaited with a thin-plate spline. */ static void cleanup_Tps(void *const tps) { Tps *const t = tps; cleanup_Mat_rm(&t->params); cleanup_Mat_rm(&t->kp_src); } /* Apply an Affine transformation to a matrix, by multiplication. The format * of Mat_in should be: * [x1 x2 ... xN * y1 y2 ... yN * ... * w1 w2 ... wN * 1 1 ... 1] * * mat_out will be resized to the appropriate size. The format will be: * [x1' x2' ... xN' * y1' y2' ... yN' * ... * w1' w2' ... wN'] * * All matrices must be initialized with init_Mat_rm prior to use. */ static int apply_Affine_Mat_rm(const void *const affine, const Mat_rm * const mat_in, Mat_rm * const mat_out) { const Affine *const aff = affine; return mul_Mat_rm(&aff->A, mat_in, mat_out); } /* Computes mat_in1 * mat_in2 = mat_out. mat_out will be resized * the appropriate size. * * All matrices must be initialized with init_Mat_rm prior to use. */ int mul_Mat_rm(const Mat_rm * const mat_in1, const Mat_rm * const mat_in2, Mat_rm * const mat_out) { int i, j, k; // Verify inputs if (mat_in1->num_cols != mat_in2->num_rows || mat_in1->type != mat_in2->type) return SIFT3D_FAILURE; // Resize mat_out mat_out->type = mat_in1->type; mat_out->num_rows = mat_in1->num_rows; mat_out->num_cols = mat_in2->num_cols; if (resize_Mat_rm(mat_out)) return SIFT3D_FAILURE; #define MAT_RM_MULTIPLY(type) \ SIFT3D_MAT_RM_LOOP_START(mat_out, i, j) \ type acc = 0; \ for (k = 0; k < mat_in1->num_cols; k++) { \ acc += SIFT3D_MAT_RM_GET(mat_in1, i, k, type) * \ SIFT3D_MAT_RM_GET(mat_in2, k, j, type); \ } \ SIFT3D_MAT_RM_GET(mat_out, i, j, type) = acc; \ SIFT3D_MAT_RM_LOOP_END // Row-major multiply switch (mat_out->type) { case SIFT3D_DOUBLE: MAT_RM_MULTIPLY(double) break; case SIFT3D_FLOAT: MAT_RM_MULTIPLY(float) break; case SIFT3D_INT: MAT_RM_MULTIPLY(int) break; default: puts("mul_Mat_rm: unknown type \n"); return SIFT3D_FAILURE; } #undef MAT_RM_MULTIPLY return SIFT3D_SUCCESS; } /* Computes the eigendecomposition of a real symmetric matrix, * A = Q * diag(L) * Q', where Q is a real orthogonal matrix and L is a real * diagonal matrix. * * A must be an [nxn] matrix. Q is [nxm], where m is in the interval [1, n], * depending on the values of A. L is [nx1], where the first m elements are * sorted in ascending order. The remaining n - m elements are zero. * * If Q is NULL, the eigenvectors will not be computed. * * The eigendecomposition is computed by divide and conquer. * * This function resizes all non-null outputs and sets their type to double. * * This function does not ensure that A is symmetric. * * All matrices must be initialized prior to calling this funciton. * All matrices must have type double. * * Note: This function computes all of the eigenvalues, to a high degree of * accuracy. A faster implementation is possible if you do not need high * precision, or if you do not need all of the eigenvalues, or if you do not * need eigenvalues outside of some interval. */ int eigen_Mat_rm(Mat_rm * A, Mat_rm * Q, Mat_rm * L) { Mat_rm A_trans; double *work; fortran_int *iwork; double lwork_ret; fortran_int info, lwork, liwork; const char jobz = Q == NULL ? 'N' : 'V'; const char uplo = 'U'; const fortran_int n = A->num_cols; const fortran_int lda = n; const fortran_int lwork_query = -1; const fortran_int liwork_query = -1; // Verify inputs if (A->num_rows != n) { puts("eigen_Mat_rm: A be square \n"); return SIFT3D_FAILURE; } if (A->type != SIFT3D_DOUBLE) { puts("eigen_Mat_rm: A must have type double \n"); return SIFT3D_FAILURE; } // Resize outputs L->num_rows = n; L->num_cols = 1; L->type = SIFT3D_DOUBLE; if (resize_Mat_rm(L)) return SIFT3D_FAILURE; // Initialize intermediate matrices and buffers work = NULL; iwork = NULL; if (init_Mat_rm(&A_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) goto EIGEN_MAT_RM_QUIT; // Copy the input matrix (A = A') if (copy_Mat_rm(A, &A_trans)) goto EIGEN_MAT_RM_QUIT; // Query for the workspace sizes dsyevd_(&jobz, &uplo, &n, A_trans.u.data_double, &lda, L->u.data_double, &lwork_ret, &lwork_query, &liwork, &liwork_query, &info); if ((int32_t) info) { printf ("eigen_Mat_rm: LAPACK dsyevd workspace query error code %d", info); goto EIGEN_MAT_RM_QUIT; } // Allocate work spaces lwork = (fortran_int) lwork_ret; if ((work = (double *)malloc(lwork * sizeof(double))) == NULL || (iwork = (fortran_int *) malloc(liwork * sizeof(fortran_int))) == NULL) goto EIGEN_MAT_RM_QUIT; // Compute the eigendecomposition dsyevd_(&jobz, &uplo, &n, A_trans.u.data_double, &lda, L->u.data_double, work, &lwork, iwork, &liwork, &info); if ((int32_t) info) { printf("eigen_Mat_rm: LAPACK dsyevd error code %d", (int) info); goto EIGEN_MAT_RM_QUIT; } // Optionally return the eigenvectors if (Q != NULL && transpose_Mat_rm(&A_trans, Q)) goto EIGEN_MAT_RM_QUIT; free(work); free(iwork); cleanup_Mat_rm(&A_trans); return SIFT3D_SUCCESS; EIGEN_MAT_RM_QUIT: if (work != NULL) free(work); if (iwork != NULL) free(iwork); cleanup_Mat_rm(&A_trans); return SIFT3D_FAILURE; } /* Solves the system AX=B exactly. A must be a square matrix. * This function first computes the reciprocal condition number of A. * If it is below the parameter "limit", it returns SIFT3D_SINGULAR. If limit * is less than 0, a default value of 100 * eps is used. * * The system is solved by LU decomposition. * * This function returns an error if A and B do not have valid dimensions. * This function resizes X to [nx1] and changes the type to match B. * All matrices must be initialized prior to calling this function. * All matrices must have type double. */ int solve_Mat_rm(const Mat_rm *const A, const Mat_rm *const B, const double limit, Mat_rm *const X) { Mat_rm A_trans, B_trans; double *work; fortran_int *ipiv, *iwork; double limit_arg, anorm, rcond; fortran_int info; const fortran_int m = A->num_rows; const fortran_int n = A->num_cols; const fortran_int nrhs = B->num_cols; const fortran_int lda = m; const fortran_int ldb = B->num_rows; const char norm_type = '1'; const char trans = 'N'; // Default parameters if (limit < 0) limit_arg = 100.0 * DBL_EPSILON; // Verify inputs if (m != n || ldb != m) { puts("solve_Mat_rm: invalid dimensions! \n"); return SIFT3D_FAILURE; } if (A->type != SIFT3D_DOUBLE || B->type != SIFT3D_DOUBLE) { puts("solve_mat_rm: All matrices must have type double \n"); return SIFT3D_FAILURE; } // Initialize intermediate matrices and buffers ipiv = NULL; work = NULL; iwork = NULL; if (init_Mat_rm(&A_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&B_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || (work = (double *)malloc(n * 4 * sizeof(double))) == NULL || (iwork = (fortran_int *) malloc(n * sizeof(fortran_int))) == NULL || (ipiv = (fortran_int *) calloc(m, sizeof(fortran_int))) == NULL) goto SOLVE_MAT_RM_QUIT; // Transpose matrices for LAPACK if (transpose_Mat_rm(A, &A_trans) || transpose_Mat_rm(B, &B_trans)) goto SOLVE_MAT_RM_QUIT; // Compute the L1-norm of A anorm = dlange_(&norm_type, &m, &n, A_trans.u.data_double, &lda, work); // Compute the LU decomposition of A in place dgetrf_(&m, &n, A_trans.u.data_double, &lda, ipiv, &info); if ((int32_t) info < 0) { printf("solve_Mat_rm: LAPACK dgetrf error code %d \n", info); goto SOLVE_MAT_RM_QUIT; } else if ((int32_t) info > 0) { goto SOLVE_MAT_RM_SINGULAR; } // Compute the reciprocal condition number of A dgecon_(&norm_type, &n, A_trans.u.data_double, &lda, &anorm, &rcond, work, iwork, &info); if ((int32_t) info) { printf("solve_Mat_rm: LAPACK dgecon error code %d \n", info); goto SOLVE_MAT_RM_QUIT; } // Return if A is singular if (rcond < limit_arg) goto SOLVE_MAT_RM_SINGULAR; // Solve the system dgetrs_(&trans, &n, &nrhs, A_trans.u.data_double, &lda, ipiv, B_trans.u.data_double, &ldb, &info); // Check for errors if ((int32_t) info) { printf("solve_Mat_rm: LAPACK dgetrs error code %d \n", info); goto SOLVE_MAT_RM_QUIT; } // Transpose results if (transpose_Mat_rm(&B_trans, X)) goto SOLVE_MAT_RM_QUIT; free(ipiv); free(work); free(iwork); cleanup_Mat_rm(&A_trans); cleanup_Mat_rm(&B_trans); return SIFT3D_SUCCESS; SOLVE_MAT_RM_SINGULAR: free(ipiv); free(work); free(iwork); cleanup_Mat_rm(&A_trans); cleanup_Mat_rm(&B_trans); return SIFT3D_SINGULAR; SOLVE_MAT_RM_QUIT: if (ipiv != NULL) free(ipiv); if (work != NULL) free(work); if (iwork != NULL) free(iwork); cleanup_Mat_rm(&A_trans); cleanup_Mat_rm(&B_trans); return SIFT3D_FAILURE; } /* Solves the system AX=B by least-squares. * * A least-norm solution is computed using the singular * value decomposition. A need not be full-rank. * * This function returns an error if A and B do not have valid dimensions. * This function resizes X to [nx1] and changes the type to match B. * All matrices must be initialized prior to calling this funciton. * All matrices must have type double. */ int solve_Mat_rm_ls(const Mat_rm *const A, const Mat_rm *const B, Mat_rm *const X) { Mat_rm A_trans, B_trans; double *s, *work; double lwork_ret; fortran_int info, rank, lwork; int i, j; const double rcond = -1; const fortran_int m = A->num_rows; const fortran_int n = A->num_cols; const fortran_int nrhs = B->num_cols; const fortran_int lda = m; const fortran_int ldb = B->num_rows; const fortran_int lwork_query = -1; // Verify inputs if (m != ldb) { puts("solve_Mat_rm_ls: invalid dimensions \n"); return SIFT3D_FAILURE; } if (A->type != SIFT3D_DOUBLE || B->type != SIFT3D_DOUBLE) { puts("solve_mat_rm_ls: All matrices must have type double \n"); return SIFT3D_FAILURE; } // Resize the output X->type = SIFT3D_DOUBLE; X->num_rows = A->num_cols; X->num_cols = B->num_cols; if (resize_Mat_rm(X)) return SIFT3D_FAILURE; // Initialize intermediate matrices and buffers s = NULL; work = NULL; if (init_Mat_rm(&A_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&B_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || (s = (double *)calloc(SIFT3D_MAX(m, n), sizeof(double))) == NULL) goto SOLVE_MAT_RM_LS_QUIT; // Transpose matrices for LAPACK if (transpose_Mat_rm(A, &A_trans) || transpose_Mat_rm(B, &B_trans)) goto SOLVE_MAT_RM_LS_QUIT; // Get the size of the workspace dgelss_(&m, &n, &nrhs, A_trans.u.data_double, &lda, B_trans.u.data_double, &ldb, s, &rcond, &rank, &lwork_ret, &lwork_query, &info); if ((int32_t) info) { printf ("solve_mat_rm: LAPACK dgelss work query error code %d \n", info); } lwork = (fortran_int) lwork_ret; // Allocate the workspace if ((work = (double *)malloc(lwork * sizeof(double))) == NULL) goto SOLVE_MAT_RM_LS_QUIT; // Solve the system dgelss_(&m, &n, &nrhs, A_trans.u.data_double, &lda, B_trans.u.data_double, &ldb, s, &rcond, &rank, work, &lwork, &info); if ((int32_t) info) { printf("solve_mat_rm: LAPACK dgelss error code %d \n", info); goto SOLVE_MAT_RM_LS_QUIT; } // Transpose results to the new leading dimension SIFT3D_MAT_RM_LOOP_START(X, i, j) SIFT3D_MAT_RM_GET(X, i, j, double) = SIFT3D_MAT_RM_GET(&B_trans, j, i, double); SIFT3D_MAT_RM_LOOP_END free(s); free(work); cleanup_Mat_rm(&A_trans); cleanup_Mat_rm(&B_trans); return SIFT3D_SUCCESS; SOLVE_MAT_RM_LS_QUIT: if (s != NULL) free(s); if (work != NULL) free(work); cleanup_Mat_rm(&A_trans); cleanup_Mat_rm(&B_trans); return SIFT3D_FAILURE; } /* Computes the trace of a matrix. trace is assumed to be the same type as * mat. Returns an error if mat is not square. * * All matrices must be initialized with init_Mat_rm prior to calling * this function. */ int trace_Mat_rm(Mat_rm * mat, void *trace) { int i; // Verify inputs if (mat->num_rows != mat->num_cols || mat->num_rows < 1) { return SIFT3D_FAILURE; } #define TRACE_MAT_RM(type) \ {\ type acc = 0; \ for (i = 0; i < mat->num_rows; i++) { \ acc += SIFT3D_MAT_RM_GET(mat, i, i, type); \ } \ *((type *) trace) = acc; \ } // Take the trace switch (mat->type) { case SIFT3D_DOUBLE: TRACE_MAT_RM(double) break; case SIFT3D_FLOAT: TRACE_MAT_RM(float) break; case SIFT3D_INT: TRACE_MAT_RM(int) break; default: puts("trace_Mat_rm: unknown type \n"); return SIFT3D_FAILURE; } #undef TRACE_MAT_RM return SIFT3D_SUCCESS; } /* Tranposes a matrix. Resizes dst with the type of src. * All matrices must be initialized prior to calling this function. */ int transpose_Mat_rm(const Mat_rm *const src, Mat_rm *const dst) { int i, j; // Verify inputs if (src->num_rows < 1 || src->num_cols < 1) return SIFT3D_FAILURE; // Resize the output dst->type = src->type; dst->num_rows = src->num_cols; dst->num_cols = src->num_rows; if (resize_Mat_rm(dst)) return SIFT3D_FAILURE; #define TRANSPOSE_MAT_RM(type) \ SIFT3D_MAT_RM_LOOP_START(src, i, j) \ SIFT3D_MAT_RM_GET(dst, j, i, type) = \ SIFT3D_MAT_RM_GET(src, i, j, type); \ SIFT3D_MAT_RM_LOOP_END // Transpose switch (src->type) { case SIFT3D_DOUBLE: TRANSPOSE_MAT_RM(double); break; case SIFT3D_FLOAT: TRANSPOSE_MAT_RM(float); break; case SIFT3D_INT: TRANSPOSE_MAT_RM(int); break; default: #ifndef NDEBUG puts("transpose_Mat_rm: unknown type \n"); #endif return SIFT3D_FAILURE; } #undef TRANSPOSE_MAT_RM return SIFT3D_SUCCESS; } /* Computes the determinant of a symmetric matrix. det is assumed to be the * same type as mat. Returns an error if mat is not square. * * This function does not verify that mat is symmetric. * * All matrices must be initialized with init_Mat_rm prior to calling * this function. */ int det_symm_Mat_rm(Mat_rm * mat, void *det) { Mat_rm matd, L; double detd; int i, j; const int n = mat->num_cols; // Verify inputs if (n < 1 || mat->num_rows != n) { puts("det_symm_Mat_rm: invalid dimensions \n"); return SIFT3D_FAILURE; } // Initialize intermediates if (init_Mat_rm(&matd, 0, 0, mat->type, SIFT3D_FALSE) || init_Mat_rm(&L, n, 1, SIFT3D_DOUBLE, SIFT3D_FALSE)) goto DET_SYMM_QUIT; // Convert the matrix to type double if (convert_Mat_rm(mat, &matd, SIFT3D_DOUBLE)) goto DET_SYMM_QUIT; // Get the eigendecomposition with LAPACK if (eigen_Mat_rm(&matd, NULL, &L)) goto DET_SYMM_QUIT; // Take the determinant detd = 0.0; SIFT3D_MAT_RM_LOOP_START(&L, i, j) detd += SIFT3D_MAT_RM_GET(&L, i, j, double); SIFT3D_MAT_RM_LOOP_END // Convert the output to the correct type switch (mat->type) { case SIFT3D_DOUBLE: *((double *)det) = detd; break; case SIFT3D_FLOAT: *((float *)det) = (float)detd; break; case SIFT3D_INT: *((int *)det) = (int)detd; break; default: puts("det_symm_Mat_rm: unknown type \n"); goto DET_SYMM_QUIT; } cleanup_Mat_rm(&matd); cleanup_Mat_rm(&L); return SIFT3D_SUCCESS; DET_SYMM_QUIT: cleanup_Mat_rm(&matd); cleanup_Mat_rm(&L); return SIFT3D_FAILURE; } /* Apply a separable filter in multiple dimensions. This function resamples the * input to have the same units as f, then resamples the output to the * original units. * * Parameters: * -src: The input image. * -dst: The filtered image. * -f: The filter to apply. * -unit: The physical units of the filter kernel. Use -1.0 for the default, * which is the same units as src. * * Return: SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int apply_Sep_FIR_filter(const Image * const src, Image * const dst, Sep_FIR_filter * const f, const double unit) { Image temp; Image *cur_src, *cur_dst; int i; const double unit_default = -1.0; // Verify inputs if (unit < 0 && unit != unit_default) { SIFT3D_ERR("apply_Sep_FIR_filter: invalid unit: %f, use " "%f for default \n", unit, unit_default); return SIFT3D_FAILURE; } // Resize the output if (im_copy_dims(src, dst)) return SIFT3D_FAILURE; // Allocate temporary storage init_im(&temp); if (im_copy_data(src, &temp)) goto apply_sep_f_quit; #define SWAP_BUFFERS \ if (cur_dst == &temp) { \ cur_src = &temp; \ cur_dst = dst; \ } else { \ cur_src = dst; \ cur_dst = &temp; \ } // Apply in n dimensions cur_src = (Image *) src; cur_dst = &temp; for (i = 0; i < IM_NDIMS; i++) { // Check for default parameters const double unit_arg = unit == unit_default ? SIFT3D_IM_GET_UNITS(src)[i] : unit; #ifdef SIFT3D_USE_OPENCL convolve_sep(cur_src, cur_dst, f, i, unit_arg); SWAP_BUFFERS #else // Transpose so that the filter dimension is x if (i != 0) { if (im_permute(cur_src, 0, i, cur_dst)) goto apply_sep_f_quit; SWAP_BUFFERS } // Apply the filter convolve_sep(cur_src, cur_dst, f, 0, unit_arg); SWAP_BUFFERS // Transpose back if (i != 0) { if (im_permute(cur_src, 0, i, cur_dst)) goto apply_sep_f_quit; SWAP_BUFFERS } #endif } // Swap back SWAP_BUFFERS; #undef SWAP_BUFFERS // Copy result to dst, if necessary if (cur_dst != dst && im_copy_data(cur_dst, dst)) goto apply_sep_f_quit; // Clean up im_free(&temp); return SIFT3D_SUCCESS; apply_sep_f_quit: im_free(&temp); return SIFT3D_FAILURE; } /* Initialize a separable FIR filter struct with the given parameters. If OpenCL * support is enabled and initialized, this creates a program to apply it with * separable filters. * * Note that the kernel data will be copied, so the user can free it without * affecting f. */ int init_Sep_FIR_filter(Sep_FIR_filter *const f, const int dim, const int width, const float *const kernel, const int symmetric) { const size_t kernel_size = width * sizeof(float); // Save the data f->dim = dim; f->width = width; f->symmetric = symmetric; // Allocate the kernel memory if ((f->kernel = (float *) malloc(kernel_size)) == NULL) { SIFT3D_ERR("init_Sep_FIT_filter: out of memory! \n"); return SIFT3D_FAILURE; } // Copy the kernel data memcpy(f->kernel, kernel, kernel_size); #ifdef SIFT3D_USE_OPENCL { char src[1 << 15]; char *template; cl_program program; cl_int err; float k; int i; const char *path = SEP_FIR_3D_PATH; const int half_width = f->half_width; // Load the template if ((template = read_file(path)) == NULL) { printf("init_Sep_FIR_Filter: error reading path %s \n", path); return SIFT3D_FAILURE; } sprintf(src, "%s\n", template); // Write the unrolled kernel for (i = -half_width; i < half_width; i++) { k = f->kernel[i]; sprintf(src, "acc += %.16f * " "read_imagef(src, sampler, center + d_xyz * %d); \n", k, i); } // Write the ending sprintf(src, "write_imagef(dst, sampler, (float4) center); \n } \n"); // Compile the program if (compile_cl_program_from_source(&program, cl_data.context, cl_data.devices, cl_data.num_devices, (char **)&src, 1)) return SIFT3D_FAILURE; f->cl_apply_unrolled = clCreateKernel(program, "sep_fir_3d", &err); check_cl_error(err, "init_Sep_FIR_Filter: create kernel"); clReleaseProgram(program); } #endif return SIFT3D_SUCCESS; } /* Free a Sep_FIR_Filter. */ void cleanup_Sep_FIR_filter(Sep_FIR_filter *const f) { if (f->kernel != NULL) { free(f->kernel); f->kernel = NULL; } #ifdef SIFT3D_USE_OPENCL //TODO release OpenCL program #endif } /* Initialize the values of im so that it can be used by the * resize function. Does not allocate memory. */ void init_im(Image *const im) { im->data = NULL; im->cl_valid = SIFT3D_FALSE; im->ux = 1; im->uy = 1; im->uz = 1; im->size = 0; im->s = -1.0; memset(SIFT3D_IM_GET_DIMS(im), 0, IM_NDIMS * sizeof(int)); memset(SIFT3D_IM_GET_STRIDES(im), 0, IM_NDIMS * sizeof(size_t)); } /* Initialize a normalized Gaussian filter, of the given sigma. * If SIFT3D_GAUSS_WIDTH_FCTR is defined, use that value for * the ratio between the width of the filter and sigma. Otherwise, * use the default value 3.0 */ #ifndef SIFT3D_GAUSS_WIDTH_FCTR #define SIFT3D_GAUSS_WIDTH_FCTR 3.0 #endif int init_Gauss_filter(Gauss_filter * const gauss, const double sigma, const int dim) { float *kernel; double x; float acc; int i; const int half_width = sigma > 0 ? SIFT3D_MAX((int)ceil(sigma * SIFT3D_GAUSS_WIDTH_FCTR), 1) : 1; const int width = 2 * half_width + 1; // Initialize intermediates if ((kernel = (float *) malloc(width * sizeof(float))) == NULL) return SIFT3D_FAILURE; // Calculate coefficients acc = 0; for (i = 0; i < width; i++) { // distance away from center of filter x = (double)i - half_width; // (x / sigma)^2 = x*x / (sigma*sigma) x /= sigma + DBL_EPSILON; // exponentiate result kernel[i] = (float)exp(-0.5 * x * x); // sum of all kernel elements acc += kernel[i]; } // normalize kernel to sum to 1 for (i = 0; i < width; i++) { kernel[i] /= acc; } // Save the filter data gauss->sigma = sigma; if (init_Sep_FIR_filter(&gauss->f, dim, width, kernel, SIFT3D_TRUE)) goto init_Gauss_filter_quit; // Clean up free(kernel); return SIFT3D_SUCCESS; init_Gauss_filter_quit: free(kernel); return SIFT3D_FAILURE; } /* Initialize a Gaussian filter to go from scale s_cur to s_next. */ int init_Gauss_incremental_filter(Gauss_filter * const gauss, const double s_cur, const double s_next, const int dim) { double sigma; if (s_cur > s_next) { SIFT3D_ERR("init_Gauss_incremental_filter: " "s_cur (%f) > s_next (%f) \n", s_cur, s_next); return SIFT3D_FAILURE; } assert(dim > 0); // Compute filter width parameter (sigma) sigma = sqrt(s_next * s_next - s_cur * s_cur); // Initialize filter kernel if (init_Gauss_filter(gauss, sigma, dim)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Free a Gauss_filter */ void cleanup_Gauss_filter(Gauss_filter * gauss) { cleanup_Sep_FIR_filter(&gauss->f); } /* Initialize a GSS filters stuct. This must be called before gss can be * used in any other functions. */ void init_GSS_filters(GSS_filters * const gss) { gss->num_filters = -1; gss->gauss_octave = NULL; } /* Create GSS filters to create the given scale-space * pyramid. */ int make_gss(GSS_filters * const gss, const Pyramid * const pyr) { Image *cur, *next; int o, s; const int dim = 3; const int num_filters = pyr->num_levels - 1; const int first_level = pyr->first_level; const int last_level = SIFT3D_PYR_LAST_LEVEL(pyr); // Verify inputs if (num_filters < 1) { SIFT3D_ERR("make_gss: pyr has only %d levels, must have " "at least 2", pyr->num_levels); return SIFT3D_FAILURE; } // Free all previous data, if any cleanup_GSS_filters(gss); init_GSS_filters(gss); // Copy pyramid parameters gss->num_filters = num_filters; gss->first_level = first_level; // Allocate the filter array (num_filters cannot be zero) if ((gss->gauss_octave = (Gauss_filter *) SIFT3D_safe_realloc(gss->gauss_octave, num_filters * sizeof(Gauss_filter))) == NULL) return SIFT3D_FAILURE; // Make the filter for the very first blur next = SIFT3D_PYR_IM_GET(pyr, pyr->first_octave, first_level); if (init_Gauss_incremental_filter(&gss->first_gauss, pyr->sigma_n, next->s, dim)) return SIFT3D_FAILURE; // Make one octave of filters (num_levels - 1) o = pyr->first_octave; for (s = first_level; s < last_level; s++) { cur = SIFT3D_PYR_IM_GET(pyr, o, s); next = SIFT3D_PYR_IM_GET(pyr, o, s + 1); if (init_Gauss_incremental_filter(SIFT3D_GAUSS_GET(gss, s), cur->s, next->s, dim)) return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Free all memory associated with the GSS filters. gss cannot be reused * unless it is reinitialized. */ void cleanup_GSS_filters(GSS_filters * const gss) { int i; const int num_filters = gss->num_filters; // We are done if gss has no filters if (num_filters < 1) return; // Free the first filter cleanup_Gauss_filter(&gss->first_gauss); // Free the octave filters for (i = 0; i < num_filters; i++) { Gauss_filter *const g = gss->gauss_octave + i; cleanup_Gauss_filter(g); } // Free the octave filter buffer free(gss->gauss_octave); } /* Initialize a Pyramid for use. Must be called before a Pyramid can be used * in any other functions. */ void init_Pyramid(Pyramid * const pyr) { pyr->levels = NULL; pyr->first_level = 0; pyr->num_levels = pyr->num_kp_levels = 0; pyr->first_octave = 0; pyr->num_octaves = 0; pyr->sigma0 = pyr->sigma_n = 0.0; } /* Resize a scale-space pyramid according to the size of base image im. * * Parameters: * -im: An image with the desired dimensions and units at octave 0 * -first_level: The index of the first pyramid level per octave * -num_kp_levels: The number of levels per octave in which keypoints are * detected * -num_levels: The total number of levels. Must be greater than or equal to * num_kp_levels. * -first_octave: The index of the first octave (0 is the base) * -num_octaves: The total number of octaves * -sigma0: The scale parameter of level 0, octave 0 * -sigma_n: The nominal scale of the image im. * -pyr: The Pyramid to be resized. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int resize_Pyramid(const Image *const im, const int first_level, const unsigned int num_kp_levels, const unsigned int num_levels, const int first_octave, const unsigned int num_octaves, Pyramid *const pyr) { double units[IM_NDIMS]; int dims[IM_NDIMS]; double factor; int i, o, s; const double sigma0 = pyr->sigma0; const double sigma_n = pyr->sigma_n; const int old_num_total_levels = pyr->num_levels * pyr->num_octaves; const int num_total_levels = num_levels * num_octaves; // Verify inputs if (num_levels < num_kp_levels) { SIFT3D_ERR("resize_Pyramid: num_levels (%u) < " "num_kp_levels (%d)", num_levels, num_kp_levels); return SIFT3D_FAILURE; } // Store the new parameters pyr->first_level = first_level; pyr->num_kp_levels = num_kp_levels; pyr->first_octave = first_octave; pyr->num_octaves = num_octaves; pyr->num_levels = num_levels; // Clean up old levels which are no longer needed for (i = num_total_levels; i < old_num_total_levels; i++) { Image *const level = pyr->levels + i; im_free(level); } // Resize the outer array if (num_total_levels != 0 && ((pyr->levels = SIFT3D_safe_realloc(pyr->levels, num_total_levels * sizeof(Image))) == NULL)) return SIFT3D_FAILURE; // We have nothing more to do if there are no levels if (num_total_levels == 0) return SIFT3D_SUCCESS; // Initalize new levels for (i = old_num_total_levels; i < num_total_levels; i++) { Image *const level = pyr->levels + i; init_im(level); } // We have nothing more to do if the image is empty if (im->data == NULL) return SIFT3D_SUCCESS; // Calculate base image dimensions and units factor = pow(2.0, -first_octave); for (i = 0; i < IM_NDIMS; i++) { dims[i] = (int) ((double) SIFT3D_IM_GET_DIMS(im)[i] * factor); units[i] = SIFT3D_IM_GET_UNITS(im)[i] * factor; } // Initialize each level separately SIFT3D_PYR_LOOP_START(pyr, o, s) // Initialize Image fields Image *const level = SIFT3D_PYR_IM_GET(pyr, o, s); memcpy(SIFT3D_IM_GET_DIMS(level), dims, IM_NDIMS * sizeof(int)); memcpy(SIFT3D_IM_GET_UNITS(level), units, IM_NDIMS * sizeof(double)); level->nc = im->nc; im_default_stride(level); // Re-size data memory if (im_resize(level)) return SIFT3D_FAILURE; SIFT3D_PYR_LOOP_SCALE_END // Adjust dimensions and recalculate image size for (i = 0; i < IM_NDIMS; i++) { dims[i] /= 2; units[i] *= 2; } SIFT3D_PYR_LOOP_OCTAVE_END // Set the scales for the new levels return set_scales_Pyramid(pyr->sigma0, pyr->sigma_n, pyr); } /* Set the scale-space parameters on a Pyramid struct. Operates on all levels * of the pyramid. This function is called automatically by resize_Pyramid. * * Parameters: * -sigma0: The scale parameter of level 0, octave 0 * -sigma_n: The nominal scale parameter of images being transfomed into * this pyramid struct. * -Pyr: The Pyramid to be modified. */ int set_scales_Pyramid(const double sigma0, const double sigma_n, Pyramid *const pyr) { int o, s; const int num_kp_levels = pyr->num_kp_levels; const Image *const first_level = SIFT3D_PYR_IM_GET(pyr, pyr->first_octave, pyr->first_level); // Compute the scales of each level SIFT3D_PYR_LOOP_START(pyr, o, s) // Compute the scale Image *const level = SIFT3D_PYR_IM_GET(pyr, o, s); const double scale = sigma0 * pow(2.0, o + (double) s / num_kp_levels); // Verify that sigma_n is not too large if (o == pyr->first_octave && s == pyr->first_level && scale < sigma_n) { SIFT3D_ERR("set_scales_Pyramid: sigma_n too large " "for these settings. Max allowed: %f \n", scale - DBL_EPSILON); return SIFT3D_FAILURE; } // Save the scale level->s = scale; SIFT3D_PYR_LOOP_END // Store the parameters pyr->sigma0 = sigma0; pyr->sigma_n = sigma_n; return SIFT3D_SUCCESS; } /* Make a deep copy of a pyramid. */ int copy_Pyramid(const Pyramid * const src, Pyramid * const dst) { Image dummy; const Image *base; int o, s, have_levels; // Initialize intermediates init_im(&dummy); // Set the scale parameters if (set_scales_Pyramid(src->sigma0, src->sigma_n, dst)) return SIFT3D_FAILURE; // Get the base image if (src->levels == NULL || src->num_octaves <= 0 || src->num_levels <= 0) { base = &dummy; have_levels = SIFT3D_FALSE; } else { base = src->levels; have_levels = SIFT3D_TRUE; } // Resize dst if (resize_Pyramid(base, src->first_level, src->num_kp_levels, src->num_levels, src->first_octave, src->num_octaves, dst)) goto copy_Pyramid_failure; // We are done if src has no levels if (!have_levels) goto copy_Pyramid_success; // Copy the levels SIFT3D_PYR_LOOP_START(dst, o, s) const Image *const src_level = SIFT3D_PYR_IM_GET(src, o, s); Image *const dst_level = SIFT3D_PYR_IM_GET(dst, o, s); if (src_level->data != NULL && im_copy_data(src_level, dst_level)) return SIFT3D_FAILURE; SIFT3D_PYR_LOOP_END copy_Pyramid_success: im_free(&dummy); return SIFT3D_SUCCESS; copy_Pyramid_failure: im_free(&dummy); return SIFT3D_FAILURE; } /* Release all memory associated with a Pyramid. pyr cannot be used again, * unless it is reinitialized. */ void cleanup_Pyramid(Pyramid * const pyr) { int o, s; // We are done if there are no levels if (pyr->levels == NULL) return; // Free the levels SIFT3D_PYR_LOOP_START(pyr, o, s) Image *const level = SIFT3D_PYR_IM_GET(pyr, o, s); im_free(level); SIFT3D_PYR_LOOP_END // Free the pyramid level buffer free(pyr->levels); } /* Initialize a Slab for first use */ void init_Slab(Slab *const slab) { slab->buf_size = slab->num = 0; slab->buf = NULL; } /* Free all memory associated with a slab. Slab cannot be re-used after * calling this function, unless re-initialized. */ void cleanup_Slab(Slab * const slab) { if (slab->buf != NULL) free(slab->buf); } /* Write the levels of a pyramid to separate files * for debugging. The path is prepended to the * octave and scale number of each image. * * File type is inferred from the extension in path. * * Supported file formats: * -NIFTI */ int write_pyramid(const char *path, Pyramid * pyr) { char path_appended[1024]; int o, s; // Validate or create output directory if (mkpath(path, out_mode)) return SIFT3D_FAILURE; // Save each image a separate file SIFT3D_PYR_LOOP_START(pyr, o, s) sprintf(path_appended, "%s_o%i_s%i", path, o, s); if (write_nii(path_appended, SIFT3D_PYR_IM_GET(pyr, o, s))) return SIFT3D_FAILURE; SIFT3D_PYR_LOOP_END return SIFT3D_SUCCESS; } /* Exit and print a message to stdout. */ void err_exit(const char *str) { SIFT3D_ERR("Error! Exiting at %s \n", str); exit(1); } /* Read a whole ASCII file into a string. Returns NULL * on error. */ SIFT3D_IGNORE_UNUSED static char *read_file(const char *path) { FILE *file; char *buf; size_t len; if ((file = fopen(path, "r")) == NULL) { return NULL; } fseek(file, 0, SEEK_END); len = ftell(file); rewind(file); if (ferror(file) || ((buf = malloc(len)) == NULL)) return NULL; fread(buf, sizeof(char), len, file); return ferror(file) ? NULL : buf; } /* Ensure all directories in the given path exist. * Thanks to Jonathan Leffler * Modifications: Ignore everything after the last '/' */ static int mkpath(const char *path, mode_t mode) { char *pp, *sp, *copypath; int status; if ((copypath = strndup(path, FILENAME_MAX)) == NULL) status = -1; /* Ignore everything after the last '/' */ if ((sp = strrchr(copypath, '/')) != NULL) { *sp = '\0'; } else { /* If there is no '/', we have nothing to do */ free(copypath); return SIFT3D_SUCCESS; } status = 0; pp = copypath; while (status == 0 && (sp = strchr(pp, '/')) != NULL) { if (sp != pp) { /* Neither root nor double slash in path */ *sp = '\0'; status = do_mkdir(copypath, mode); *sp = '/'; } pp = sp + 1; } if (status == 0) status = do_mkdir(copypath, mode); free(copypath); return (status); } /* Make a directory if it does not exist. * Thanks to Jonathan Leffler */ static int do_mkdir(const char *path, mode_t mode) { struct stat st; int status = 0; if (stat(path, &st) != 0) { /* Directory does not exist. EEXIST for race condition */ if (cross_mkdir(path, mode) != 0 && errno != EEXIST) status = -1; } else if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; status = -1; } return (status); } /* Cross-platform mkdir */ static int cross_mkdir(const char *path, mode_t mode) { #ifdef _MINGW_WINDOWS return mkdir(path); #elif defined( _WINDOWS ) return _mkdir(path); #else return mkdir(path, mode); #endif } /* Initialize a Tps struct. This initializes * all fields, and allocates memory for the inner * matrix, initializing it to zero. */ int init_Tps(Tps * tps, int dim, int terms) { // Verify inputs if (dim < 2) return SIFT3D_FAILURE; // Initialize the type tps->tform.type = TPS; // Initialize the vtable tps->tform.vtable = &Tps_vtable; // Initialize the matrices if (init_Mat_rm(&tps->params, dim, terms, SIFT3D_DOUBLE, SIFT3D_TRUE)) return SIFT3D_FAILURE; if (init_Mat_rm(&tps->kp_src, terms - dim - 1, dim, SIFT3D_DOUBLE, SIFT3D_TRUE)) return SIFT3D_FAILURE; tps->dim = dim; return SIFT3D_SUCCESS; } /* Initialize a RANSAC struct with the default parameters */ void init_Ransac(Ransac *const ran) { ran->err_thresh = SIFT3D_err_thresh_default; ran->num_iter = SIFT3D_num_iter_default; } /* Set the err_thresh parameter in a Ransac struct, checking for validity. */ int set_err_thresh_Ransac(Ransac *const ran, double err_thresh) { if (err_thresh < 0.0) { SIFT3D_ERR("set_err_thresh_Ransac: invalid error " "threshold: %f \n", err_thresh); return SIFT3D_FAILURE; } ran->err_thresh = err_thresh; return SIFT3D_SUCCESS; } /* Set the num_iter parameter in a Ransac struct. */ int set_num_iter_Ransac(Ransac *const ran, int num_iter) { if (num_iter < 1) { SIFT3D_ERR("set_num_iter_Ransac: invalid number of " "iterations: %d \n", num_iter); return SIFT3D_FAILURE; } ran->num_iter = num_iter; return SIFT3D_SUCCESS; } /* Copy a Ransac struct from src to dst. */ int copy_Ransac(const Ransac *const src, Ransac *const dst) { return set_num_iter_Ransac(dst, src->num_iter) || set_err_thresh_Ransac(dst, src->err_thresh); } /* Returns an array of k integers, (uniformly) randomly chosen from the * integers 0 through n - 1. * * The value of *ret must either be NULL, or a pointer to a previously * allocated block. On successful return, *ret contains the k random integers. * * Returns SIFT3D_SUCCESS on succes, SIFT3D_FAILURE otherwise. */ static int n_choose_k(const int n, const int k, int **ret) { int i; // Verify inputs if (n < k || k < 1) goto n_choose_k_fail; // Allocate the array of n elements if ((*ret = malloc(n * sizeof(int))) == NULL) goto n_choose_k_fail; // Initialize the array of indices for (i = 0; i < n; i++) { (*ret)[i] = i; } // Randomize the first k indices using Knuth shuffles for (i = 0; i < k; i++) { int *const ints = *ret; const int temp = ints[i]; const int rand_idx = i + rand() % (n - i); ints[i] = ints[rand_idx]; ints[rand_idx] = temp; } // Release unused memory if ((*ret = SIFT3D_safe_realloc(*ret, k * sizeof(int))) == NULL) goto n_choose_k_fail; return SIFT3D_SUCCESS; n_choose_k_fail: if (*ret != NULL) { free(*ret); *ret = NULL; } return SIFT3D_FAILURE; } //make the system matrix for spline SIFT3D_IGNORE_UNUSED static int make_spline_matrix(Mat_rm * src, Mat_rm * src_in, Mat_rm * sp_src, int K_terms, int *r, int dim) { int i, d; double x, y, z, x2, y2, z2, r_sq, U; src_in->type = SIFT3D_DOUBLE; sp_src->type = SIFT3D_DOUBLE; if (init_Mat_rm (src_in, K_terms + dim + 1, K_terms + dim + 1, SIFT3D_DOUBLE, SIFT3D_TRUE)) { return SIFT3D_FAILURE; } if (init_Mat_rm(sp_src, K_terms, dim, SIFT3D_DOUBLE, SIFT3D_TRUE)) { return SIFT3D_FAILURE; } for (i = 0; i < K_terms; i++) { //get the coordinate of current point switch (dim) { case 2: x = SIFT3D_MAT_RM_GET(src, r[i], 0, double); y = SIFT3D_MAT_RM_GET(src, r[i], 1, double); break; case 3: x = SIFT3D_MAT_RM_GET(src, r[i], 0, double); y = SIFT3D_MAT_RM_GET(src, r[i], 1, double); z = SIFT3D_MAT_RM_GET(src, r[i], 2, double); break; } for (d = 0; d < i; d++) { //compute r switch (dim) { case 2: x2 = SIFT3D_MAT_RM_GET(src, r[d], 0, double); y2 = SIFT3D_MAT_RM_GET(src, r[d], 1, double); r_sq = (x - x2) * (x - x2) + (y - y2) * (y - y2); break; case 3: x2 = SIFT3D_MAT_RM_GET(src, r[d], 0, double); y2 = SIFT3D_MAT_RM_GET(src, r[d], 1, double); z2 = SIFT3D_MAT_RM_GET(src, r[d], 2, double); r_sq = (x - x2) * (x - x2) + (y - y2) * (y - y2) + (z - z2) * (z - z2); break; } //compute U U = r_sq * log(r_sq); //construct K SIFT3D_MAT_RM_GET(src_in, i, d, double) = U; SIFT3D_MAT_RM_GET(src_in, d, i, double) = U; } SIFT3D_MAT_RM_GET(src_in, i, i, double) = 0.0; //construct P and P' SIFT3D_MAT_RM_GET(src_in, i, K_terms, double) = 1.0; SIFT3D_MAT_RM_GET(src_in, K_terms, i, double) = 1.0; switch (dim) { case 2: SIFT3D_MAT_RM_GET(src_in, i, K_terms + 1, double) = x; SIFT3D_MAT_RM_GET(src_in, i, K_terms + 2, double) = y; SIFT3D_MAT_RM_GET(src_in, K_terms + 1, i, double) = x; SIFT3D_MAT_RM_GET(src_in, K_terms + 2, i, double) = y; break; case 3: SIFT3D_MAT_RM_GET(src_in, i, K_terms + 1, double) = x; SIFT3D_MAT_RM_GET(src_in, i, K_terms + 2, double) = y; SIFT3D_MAT_RM_GET(src_in, i, K_terms + 3, double) = z; SIFT3D_MAT_RM_GET(src_in, K_terms + 1, i, double) = x; SIFT3D_MAT_RM_GET(src_in, K_terms + 2, i, double) = y; SIFT3D_MAT_RM_GET(src_in, K_terms + 3, i, double) = z; break; } //construct sp_src matrix(matrix that stores control points) switch (dim) { case 2: SIFT3D_MAT_RM_GET(sp_src, i, 0, double) = x; SIFT3D_MAT_RM_GET(sp_src, i, 1, double) = y; break; case 3: SIFT3D_MAT_RM_GET(sp_src, i, 0, double) = x; SIFT3D_MAT_RM_GET(sp_src, i, 1, double) = y; SIFT3D_MAT_RM_GET(sp_src, i, 2, double) = z; break; } } //construct O for (i = 0; i < dim; i++) { for (d = 0; d < dim; d++) { SIFT3D_MAT_RM_GET(src_in, K_terms + i, K_terms + d, double) = 0.0; } } return SIFT3D_SUCCESS; } //make the system matrix for affine static int make_affine_matrix(const Mat_rm *const pts_in, const int dim, Mat_rm *const mat_out) { int i, j; const int num_rows = pts_in->num_rows; mat_out->type = SIFT3D_DOUBLE; mat_out->num_rows = num_rows; mat_out->num_cols = dim + 1; if (resize_Mat_rm(mat_out)) return SIFT3D_FAILURE; for (i = 0; i < num_rows; i++) { //Add one row to the matrix for (j = 0; j < dim; j++) { SIFT3D_MAT_RM_GET(mat_out, i, j, double) = SIFT3D_MAT_RM_GET(pts_in, i, j, double); } SIFT3D_MAT_RM_GET(mat_out, i, dim, double) = 1.0; } return SIFT3D_SUCCESS; } //extract the control matrix from tform struct (only valid for spline) SIFT3D_IGNORE_UNUSED static Mat_rm *extract_ctrl_pts(void *tform, tform_type type) { Mat_rm *T; Tps *tps = (Tps *) tform; switch (type) { case TPS: T = extract_ctrl_pts_Tps(tps); break; case AFFINE: break; default: return NULL; } return T; } static Mat_rm *extract_ctrl_pts_Tps(Tps * tps) { Mat_rm *kp_src = &tps->kp_src; return kp_src; } /* Solve for a transformation struct. * * Paramters: * src - See ransac(). * ref - See ransac() * tform - See ransac() * * Returns SIFT3D_SUCCESS, SIFT3D_SINGULAR, or SIFT3D_FAILURE. See ransac() for * interpretation. */ static int solve_system(const Mat_rm *const src, const Mat_rm *const ref, void *const tform) { const tform_type type = tform_get_type(tform); //Mat_rm *kp_ref; Mat_rm ref_sys, X; int dim, ret; init_Mat_rm(&ref_sys, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE); init_Mat_rm(&X, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE); //construct source matrix and initialize reference vector switch (type) { case TPS: //kp_ref = extract_ctrl_pts(tform, type); // make_spline_matrix(ref, &ref_in, kp_ref, num_pts, r, dim); puts("solve_system: TPS not yet implemented"); goto SOLVE_SYSTEM_FAIL; case AFFINE: dim = AFFINE_GET_DIM((Affine *const) tform); make_affine_matrix(ref, dim, &ref_sys); break; default: puts("solve_system: unknown type"); goto SOLVE_SYSTEM_FAIL; } // solve for the coefficients ret = ref_sys.num_rows == ref_sys.num_cols ? solve_Mat_rm(&ref_sys, src, -1.0, &X) : solve_Mat_rm_ls(&ref_sys, src, &X); switch (ret) { case SIFT3D_SUCCESS: break; case SIFT3D_SINGULAR: goto SOLVE_SYSTEM_SINGULAR; default: goto SOLVE_SYSTEM_FAIL; } // Save the transformation matrix switch (type) { case TPS: //TODO goto SOLVE_SYSTEM_FAIL; case AFFINE: { Mat_rm X_trans; init_Mat_rm(&X_trans, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE); ret = transpose_Mat_rm(&X, &X_trans) || Affine_set_mat(&X_trans, (Affine *) tform); cleanup_Mat_rm(&X_trans); if (ret) goto SOLVE_SYSTEM_FAIL; break; } default: goto SOLVE_SYSTEM_FAIL; } // Clean up cleanup_Mat_rm(&ref_sys); cleanup_Mat_rm(&X); return SIFT3D_SUCCESS; SOLVE_SYSTEM_SINGULAR: cleanup_Mat_rm(&ref_sys); cleanup_Mat_rm(&X); return SIFT3D_SINGULAR; SOLVE_SYSTEM_FAIL: cleanup_Mat_rm(&ref_sys); cleanup_Mat_rm(&X); return SIFT3D_FAILURE; } //Find the SSD error for the i'th point static double tform_err_sq(const void *const tform, const Mat_rm *const src, const Mat_rm *const ref, const int i) { double err = 0.0; //Initialization //in -- inputs coordinates of source points //out -- registered points //r -- reference points (ground truth) double x_in, y_in, z_in, x_r, y_r, z_r, x_out, y_out, z_out; //Find the source point x_in = SIFT3D_MAT_RM_GET(ref, i, 0, double); y_in = SIFT3D_MAT_RM_GET(ref, i, 1, double); z_in = SIFT3D_MAT_RM_GET(ref, i, 2, double); //Register apply_tform_xyz(tform, x_in, y_in, z_in, &x_out, &y_out, &z_out); //Find the reference point x_r = SIFT3D_MAT_RM_GET(src, i, 0, double); y_r = SIFT3D_MAT_RM_GET(src, i, 1, double); z_r = SIFT3D_MAT_RM_GET(src, i, 2, double); //Find the SSD error err = (x_r - x_out) * (x_r - x_out) + (y_r - y_out) * (y_r - y_out) + (z_r - z_out) * (z_r - z_out); //return the result return err; } /* Perform one iteration of RANSAC. * * Parameters: * src - The source points. * ref - The reference points. * tform - The output transformation. Must be initialized. * cset - An array in which to store the concensus set. The value *cset must * either be NULL, or a pointer to a previously allocated block. * len - A location in which to store the length of the cset. * * Returns SIFT3D_SUCCESS on success, SIFT3D_SINGULAR if the system is * near singular, and SIFT3D_FAILURE otherwise. */ static int ransac(const Mat_rm *const src, const Mat_rm *const ref, const Ransac *const ran, void *tform, int **const cset, int *const len) { int *rand_indices; Mat_rm src_rand, ref_rand; int i, j, num_rand, cset_len; const double err_thresh = ran->err_thresh; const double err_thresh_sq = err_thresh * err_thresh; const int num_pts = src->num_rows; const int num_dim = src->num_cols; const tform_type type = tform_get_type(tform); // Verify inputs if (src->type != SIFT3D_DOUBLE || src->type != ref->type) { puts("ransac: all matrices must have type double \n"); return SIFT3D_FAILURE; } if (src->num_rows != ref->num_rows || src->num_cols != ref->num_cols) { puts("ransac: src and ref must have the same dimensions \n"); return SIFT3D_FAILURE; } // Get the number of points for this transform switch (type) { case AFFINE: num_rand = AFFINE_GET_DIM((Affine *const) tform) + 1; break; default: printf("ransac: unknown transformation type \n"); return SIFT3D_FAILURE; } // Initialize intermediates rand_indices = NULL; init_Mat_rm(&src_rand, num_rand, num_dim, SIFT3D_DOUBLE, SIFT3D_FALSE); init_Mat_rm(&ref_rand, num_rand, num_dim, SIFT3D_DOUBLE, SIFT3D_FALSE); // Draw random point indices if (n_choose_k(num_pts, num_rand, &rand_indices)) goto RANSAC_FAIL; // Copy the random points SIFT3D_MAT_RM_LOOP_START(&src_rand, i, j) const int rand_idx = rand_indices[i]; SIFT3D_MAT_RM_GET(&src_rand, i, j, double) = SIFT3D_MAT_RM_GET(src, rand_idx, j, double); SIFT3D_MAT_RM_GET(&ref_rand, i, j, double) = SIFT3D_MAT_RM_GET(ref, rand_idx, j, double); SIFT3D_MAT_RM_LOOP_END // Fit a transform to the random points switch (solve_system(&src_rand, &ref_rand, tform)) { case SIFT3D_SUCCESS: break; case SIFT3D_SINGULAR: goto RANSAC_SINGULAR; default: goto RANSAC_FAIL; } // Extract the consensus set cset_len = 0; for (i = 0; i < num_pts; i++) { // Calculate the error const double err_sq = tform_err_sq(tform, src, ref, i); // Reject points below the error threshold if (err_sq > err_thresh_sq) continue; // Add to the consensus set (++cset_len cannot be zero) if ((*cset = SIFT3D_safe_realloc(*cset, ++cset_len * sizeof(int))) == NULL) goto RANSAC_FAIL; (*cset)[cset_len - 1] = i; } // Return the new length of cset *len = cset_len; if (rand_indices != NULL) free(rand_indices); cleanup_Mat_rm(&src_rand); cleanup_Mat_rm(&ref_rand); return SIFT3D_SUCCESS; RANSAC_SINGULAR: if (rand_indices != NULL) free(rand_indices); cleanup_Mat_rm(&src_rand); cleanup_Mat_rm(&ref_rand); return SIFT3D_SINGULAR; RANSAC_FAIL: if (rand_indices != NULL) free(rand_indices); cleanup_Mat_rm(&src_rand); cleanup_Mat_rm(&ref_rand); return SIFT3D_FAILURE; } //Resize spline struct based on number of selected points int resize_Tps(Tps * tps, int num_pts, int dim) { Mat_rm *params = &(tps->params); Mat_rm *kp_src = &(tps->kp_src); params->num_cols = num_pts + dim + 1; params->num_rows = dim; kp_src->num_rows = num_pts; kp_src->num_cols = dim; if (resize_Mat_rm(params)) { return SIFT3D_FAILURE; } if (resize_Mat_rm(kp_src)) { return SIFT3D_FAILURE; } tps->dim = dim; return SIFT3D_SUCCESS; } /* Fit a transformation from ref to src points, using random sample concensus * (RANSAC). * * Parameters: * ran - Struct storing RANSAC parameters. * src - The [mxn] source points. * ref - The [mxn] reference points. * tform - The output transform. Must be initialized with init_from prior to * calling this function. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int find_tform_ransac(const Ransac *const ran, const Mat_rm *const src, const Mat_rm *const ref, void *const tform) { Mat_rm ref_cset, src_cset; void *tform_cur; int *cset, *cset_best; int i, j, dim, num_terms, ret, len, len_best, min_num_inliers; const int num_iter = ran->num_iter; const int num_pts = src->num_rows; const size_t tform_size = tform_get_size(tform); const tform_type type = tform_get_type(tform); // Initialize data structures cset = cset_best = NULL; len_best = 0; if ((tform_cur = malloc(tform_size)) == NULL || init_tform(tform_cur, type) || init_Mat_rm(&src_cset, len_best, IM_NDIMS, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&ref_cset, len_best, IM_NDIMS, SIFT3D_DOUBLE, SIFT3D_FALSE)) goto find_tform_quit; // initialize type-specific variables switch (type) { case AFFINE: dim = AFFINE_GET_DIM((Affine *const) tform); num_terms = dim + 1; min_num_inliers = 5; break; default: puts("find_tform_ransac: unsupported transformation " "type \n"); goto find_tform_quit; } if (num_pts < num_terms) { printf("Not enough matched points \n"); goto find_tform_quit; } // Ransac iterations for (i = 0; i < num_iter; i++) { do { ret = ransac(src, ref, ran, tform_cur, &cset, &len); } while (ret == SIFT3D_SINGULAR); if (ret == SIFT3D_FAILURE) goto find_tform_quit; if (len > len_best) { len_best = len; if ((cset_best = (int *)SIFT3D_safe_realloc(cset_best, len * sizeof(int))) == NULL || copy_tform(tform_cur, tform)) goto find_tform_quit; memcpy(cset_best, cset, len * sizeof(int)); } } // Check if the minimum number of inliers was found if (len_best < min_num_inliers) { puts("find_tform_ransac: No good model was found! \n"); goto find_tform_quit; } // Resize the concensus set matrices src_cset.num_rows = ref_cset.num_rows = len_best; if (resize_Mat_rm(&src_cset) || resize_Mat_rm(&ref_cset)) goto find_tform_quit; // Extract the concensus set SIFT3D_MAT_RM_LOOP_START(&src_cset, i, j) const int idx = cset_best[i]; SIFT3D_MAT_RM_GET(&src_cset, i, j, double) = SIFT3D_MAT_RM_GET(src, idx, j, double); SIFT3D_MAT_RM_GET(&ref_cset, i, j, double) = SIFT3D_MAT_RM_GET(ref, idx, j, double); SIFT3D_MAT_RM_LOOP_END #ifdef SIFT3D_RANSAC_REFINE // Refine with least squares switch (solve_system(&src_cset, &ref_cset, tform_cur)) { case SIFT3D_SUCCESS: // Copy the refined transformation to the output if (copy_tform(tform_cur, tform)) goto find_tform_quit; break; case SIFT3D_SINGULAR: // Stick with the old transformation #ifdef VERBOSE printf("find_tform_ransac: warning: least-squares refinement " "abandoned due to numerical precision \n"); #endif break; default: goto find_tform_quit; } #endif // Clean up free(cset); free(cset_best); cleanup_tform(tform_cur); cleanup_Mat_rm(&ref_cset); cleanup_Mat_rm(&src_cset); if (tform_cur != NULL) free(tform_cur); return SIFT3D_SUCCESS; find_tform_quit: // Clean up and return an error if (cset != NULL) free(cset); if (cset_best != NULL) free(cset_best); cleanup_tform(tform_cur); if (tform_cur != NULL) free(tform_cur); cleanup_Mat_rm(&ref_cset); cleanup_Mat_rm(&src_cset); return SIFT3D_FAILURE; } /* Parse the GNU standard arguments (--version, --help). On return, the * getopt state is restored to the original. * * Return values: * -SIFT3D_HELP - "--help" was found * -SIFT3D_VERSION - "--version" was found, and the version message printed * -SIFT3D_FALSE - no GNU standard arguments were found */ int parse_gnu(const int argc, char *const *argv) { int c; const int opterr_start = opterr; // Options const struct option longopts[] = { {"help", no_argument, NULL, SIFT3D_HELP}, {"version", no_argument, NULL, SIFT3D_VERSION}, {0, 0, 0, 0} }; // Process the arguments opterr = 0; while ((c = getopt_long(argc, argv, "+", longopts, NULL)) != -1) { switch (c) { case SIFT3D_HELP: return SIFT3D_HELP; case SIFT3D_VERSION: puts(version_msg); return SIFT3D_VERSION; } } // Restore the state optind = 0; opterr = opterr_start; return SIFT3D_FALSE; } /* Print the bug message to stderr. */ void print_bug_msg() { SIFT3D_ERR(bug_msg); } ================================================ FILE: imutil/imutil.h ================================================ /* ----------------------------------------------------------------------------- * imutil.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Public header for imutil.c * ----------------------------------------------------------------------------- */ #include "imtypes.h" #ifndef _IMUTIL_H #define _IMUTIL_H #ifdef __cplusplus extern "C" { #endif /* Extra return codes for this module */ #define SIFT3D_FILE_DOES_NOT_EXIST 1 /* The file does not exist */ #define SIFT3D_UNSUPPORTED_FILE_TYPE 2 /* The file type is not supported */ #define SIFT3D_WRAPPER_NOT_COMPILED 3 /* The file type is supported, but the * wrapper library was not compiled. */ #define SIFT3D_UNEVEN_SPACING 4 /* The image slices are not evenly spaced. */ #define SIFT3D_INCONSISTENT_AXES 5 /* The image slices have inconsistent * axes. */ #define SIFT3D_DUPLICATE_SLICES 6 /* Multiple slices in the same location. */ /* Vendor-specific info */ #define PLATFORM_NAME_NVIDIA "NVIDIA CUDA" /* Parameters */ const extern double SIFT3D_err_thresh_default; const extern int SIFT3D_num_iter_default; /* Externally-visible routines */ void *SIFT3D_safe_realloc(void *ptr, size_t size); void clFinish_all(); void check_cl_error(int err, const char *msg); int init_cl(CL_data *user_cl_data, const char *platform_name, cl_device_type device_type, cl_mem_flags mem_flags, cl_image_format image_format); void init_Mesh(Mesh * const mesh); void cleanup_Mesh(Mesh * const mesh); int convert_Mat_rm(const Mat_rm *const in, Mat_rm *const out, const Mat_rm_type type); int init_Mat_rm(Mat_rm *const mat, const int num_rows, const int num_cols, const Mat_rm_type type, const int set_zero); int init_Mat_rm_p(Mat_rm *const mat, const void *const p, const int num_rows, const int num_cols, const Mat_rm_type type, const int set_zero); void sprint_type_Mat_rm(const Mat_rm *const mat, char *const str); int concat_Mat_rm(const Mat_rm * const src1, const Mat_rm * const src2, Mat_rm * const dst, const int dim); int set_Mat_rm_zero(Mat_rm *mat); int copy_Mat_rm(const Mat_rm *const src, Mat_rm *const dst); int print_Mat_rm(const Mat_rm *const mat); int resize_Mat_rm(Mat_rm *const mat); int eigen_Mat_rm(Mat_rm *A, Mat_rm *Q, Mat_rm *L); int solve_Mat_rm(const Mat_rm *const A, const Mat_rm *const B, const double limit, Mat_rm *const X); int solve_Mat_rm_ls(const Mat_rm *const A, const Mat_rm *const B, Mat_rm *const X); int transpose_Mat_rm(const Mat_rm *const src, Mat_rm *const dst); int det_symm_Mat_rm(Mat_rm *mat, void *det); int zero_Mat_rm(Mat_rm *const mat); int identity_Mat_rm(const int n, Mat_rm *const mat); void cleanup_Mat_rm(Mat_rm *mat); int init_tform(void *const tform, const tform_type type); int init_Affine(Affine *const affine, const int dim); int copy_tform(const void *const src, void *const dst); int Affine_set_mat(const Mat_rm *const mat, Affine *const affine); void apply_tform_xyz(const void *const tform, const double x_in, const double y_in, const double z_in, double *const x_out, double *const y_out, double *const z_out); int apply_tform_Mat_rm(const void *const tform, const Mat_rm *const mat_in, Mat_rm *const mat_out); tform_type tform_get_type(const void *const tform); size_t tform_get_size(const void *const tform); size_t tform_type_get_size(const tform_type type); void cleanup_tform(void *const tform); int write_tform(const char *path, const void *const tform); int mul_Mat_rm(const Mat_rm *const mat_in1, const Mat_rm *const mat_in2, Mat_rm *const mat_out); int draw_grid(Image *grid, int nx, int ny, int nz, int spacing, int line_width); int draw_points(const Mat_rm *const in, const int *const dims, int radius, Image *const out); int draw_lines(const Mat_rm *const points1, const Mat_rm *const points2, const int *const dims, Image *const out); im_format im_get_format(const char *path); int im_read(const char *path, Image *const im); int im_write(const char *path, const Image *const im); char *im_get_parent_dir(const char *path); int write_Mat_rm(const char *path, const Mat_rm *const mat); int init_im_with_dims(Image *const im, const int nx, const int ny, const int nz, const int nc); int im_load_cl(Image *im, int blocking); int im_copy_dims(const Image *const src, Image *dst); int im_copy_data(const Image *const src, Image *const dst); void im_free(Image *im); int im_channel(const Image * const src, Image * const dst, const unsigned int chan); int im_downsample_2x(const Image *const src, Image *const dst); int im_downsample_2x_cl(Image *src, Image *dst); int im_read_back(Image *im, int blocking); int im_set_kernel_arg(cl_kernel kernel, int n, Image *im); int im_permute(const Image *const src, const int dim1, const int dim2, Image *const dst); int im_upsample_2x(const Image *const src, Image *const dst); int im_pad(const Image *const im, Image *const pad); void im_default_stride(Image *const im); int im_resize(Image *const im); int im_concat(const Image *const src1, const Image *const src2, const int dim, Image *const dst); float im_max_abs(const Image *const im); void im_scale(const Image *const im); int im_subtract(Image *src1, Image *src2, Image *dst); void im_zero(Image *im); void im_Hessian(Image *im, int x, int y, int z, Mat_rm *H); int im_inv_transform(const void *const tform, const Image * const src, const interp_type interp, const int resize, Image *const dst); int im_resample(const Image *const src, const double *const units, const interp_type interp, Image *const dst); void init_im(Image *const im); int init_Gauss_filter(Gauss_filter *const gauss, const double sigma, const int dim); int init_Gauss_incremental_filter(Gauss_filter *const gauss, const double s_cur, const double s_next, const int dim); int init_Sep_FIR_filter(Sep_FIR_filter *const f, const int dim, const int width, const float *const kernel, const int symmetric); int apply_Sep_FIR_filter(const Image *const src, Image *const dst, Sep_FIR_filter *const f, const double unit); void cleanup_Sep_FIR_filter(Sep_FIR_filter *const f); void cleanup_Gauss_filter(Gauss_filter *gauss); void init_GSS_filters(GSS_filters *const gss); int make_gss(GSS_filters *const gss, const Pyramid *const pyr); void cleanup_GSS_filters(GSS_filters *const gss); void init_Pyramid(Pyramid *const pyr); int copy_Pyramid(const Pyramid *const src, Pyramid *const dst); int resize_Pyramid(const Image *const im, const int first_level, const unsigned int num_kp_levels, const unsigned int num_levels, const int first_octave, const unsigned int num_octaves, Pyramid *const pyr); int set_scales_Pyramid(const double sigma0, const double sigma_n, Pyramid *const pyr); void cleanup_Pyramid(Pyramid *const pyr); void init_Slab(Slab *const slab); void cleanup_Slab(Slab *const slab); int resize_Slab(Slab *slab, int num, size_t size); int write_pyramid(const char *path, Pyramid *pyr); void err_exit(const char *str); void init_Ransac(Ransac *const ran); int set_err_thresh_Ransac(Ransac *const ran, double err_thresh); int set_num_iter_Ransac(Ransac *const ran, int num_iter); int copy_Ransac(const Ransac *const src, Ransac *const dst); int find_tform_ransac(const Ransac *const ran, const Mat_rm *const src, const Mat_rm *const ref, void *const tform); int parse_gnu(const int argc, char *const *argv); void print_bug_msg(); #ifdef __cplusplus } #endif #endif ================================================ FILE: imutil/kernels.cl ================================================ /* ----------------------------------------------------------------------------- * kernels.cl * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains OpenCL kernels for image processing. * ----------------------------------------------------------------------------- */ #pragma OPENCL EXTENSION cl_khr_3d_image_writes : enable sampler_t sampler_downsample_2x = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAEREST; kernel void downsample_2x_3d(__read_only image3d_t src, __write_only image3d_t dst) { int x, y, z; float4 out; x = get_global_id(0); y = get_global_id(1); z = get_global_id(2); out = read_imagef(src, sampler_downsample_2x, (int4) (x, y, z, 0) * 2); write_imagef(dst, (int4) (x, y, z, 0), out); } ================================================ FILE: imutil/nifti.c ================================================ /* ----------------------------------------------------------------------------- * nifti.c * ----------------------------------------------------------------------------- * Copyright (c) 2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Implementation of the nifticlib wrapper for reading and writing NIFTI images. * ----------------------------------------------------------------------------- */ /* SIFT3D includes */ #include "imutil.h" #include "immacros.h" #include "nifti.h" #ifndef SIFT3D_WITH_NIFTI /* Return error messages if this was not compiled with NIFTI support. */ static int nii_error_message() { SIFT3D_ERR("nii_error_message: SIFT3D was not compiled with NIFTI " "support!\n"); return SIFT3D_WRAPPER_NOT_COMPILED; } int read_nii(const char *path, Image *const im) { return nii_error_message(); } int write_nii(const char *path, const Image *const im) { return nii_error_message(); } #else /* Standard includes */ #include #include #include /* Nifti includes */ #include /* Macro returning NIFTI data index for the channel dimension */ #define SIFT3D_NIM_GET_IDX(im, x, y, z, c) ( \ (x) + (y) * (im)->nx + (z) * (im)->nx * (im)->ny + \ (c) * (im)->nx * (im)->ny * (im)->nz ) /* Helper function to read a NIFTI image (.nii, .nii.gz). * Prior to calling this function, use init_im(im). * This function allocates memory. */ int read_nii(const char *path, Image *const im) { nifti_image *nifti; double slope; int x, y, z, c, i, dim_counter; const size_t nim_channel_stride = im->nx * im->ny * im->nz; // Read NIFTI file if ((nifti = nifti_image_read(path, 1)) == NULL) { SIFT3D_ERR("read_nii: failure loading file %s", path); return SIFT3D_FAILURE; } // Find the dimensionality of the array, given by the last dimension // greater than 1. Note that the dimensions begin at dim[1]. for (dim_counter = nifti->ndim; dim_counter > 0; dim_counter--) { if (nifti->dim[dim_counter] > 1) { break; } } // Check the dimensionality. 4D is interpreted as a 3D array with // multiple channels. if (dim_counter > 4) { SIFT3D_ERR("read_nii: file %s has unsupported " "dimensionality %d\n", path, dim_counter); goto read_nii_quit; } // Fill the trailing dimensions with 1 for (i = dim_counter; i < IM_NDIMS; i++) { SIFT3D_IM_GET_DIMS(im)[i] = 1; } // Store the real world coordinates im->ux = nifti->dx; im->uy = nifti->dy; im->uz = nifti->dz; // Resize im im->nx = nifti->nx; im->ny = nifti->ny; im->nz = nifti->nz; im->nc = dim_counter == 4 ? nifti->nt : 1; im_default_stride(im); im_resize(im); // Ignore the slope if it's zero. This is an ill-formatted image. slope = nifti->scl_slope; if (slope == 0.0) slope = 1.0; // Macro to copy the data for each type #define IM_COPY_FROM_TYPE(type) \ SIFT3D_IM_LOOP_START_C(im, x, y, z, c) \ SIFT3D_IM_GET_VOX(im, x, y, z, c) = (float) ( \ (double) ((type *) nifti->data)[\ SIFT3D_NIM_GET_IDX(im, x, y, z, c)] * \ (double) slope + (double) nifti->scl_inter); \ SIFT3D_IM_LOOP_END_C // Copy the data into im, applying the slope and intercept switch (nifti->datatype) { case NIFTI_TYPE_UINT8: IM_COPY_FROM_TYPE(uint8_t); break; case NIFTI_TYPE_INT8: IM_COPY_FROM_TYPE(int8_t); break; case NIFTI_TYPE_UINT16: IM_COPY_FROM_TYPE(uint16_t); break; case NIFTI_TYPE_INT16: IM_COPY_FROM_TYPE(int16_t); break; case NIFTI_TYPE_UINT32: IM_COPY_FROM_TYPE(uint32_t); break; case NIFTI_TYPE_INT32: IM_COPY_FROM_TYPE(int32_t); break; case NIFTI_TYPE_UINT64: IM_COPY_FROM_TYPE(uint64_t); break; case NIFTI_TYPE_INT64: IM_COPY_FROM_TYPE(int64_t); break; case NIFTI_TYPE_FLOAT32: IM_COPY_FROM_TYPE(float); break; case NIFTI_TYPE_FLOAT64: IM_COPY_FROM_TYPE(double); break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX128: case NIFTI_TYPE_COMPLEX256: case NIFTI_TYPE_COMPLEX64: default: SIFT3D_ERR("read_nii: unsupported datatype %s \n", nifti_datatype_string(nifti->datatype)); goto read_nii_quit; } #undef IM_COPY_FROM_TYPE // Clean up NIFTI data nifti_free_extensions(nifti); nifti_image_free(nifti); return SIFT3D_SUCCESS; read_nii_quit: nifti_free_extensions(nifti); nifti_image_free(nifti); return SIFT3D_FAILURE; } /* Write a Image to the specified path, in NIFTI format. * The path extension must be one of (.nii, .nii.gz). */ int write_nii(const char *path, const Image *const im) { nifti_image *nifti; int x, y, z, c; const size_t nim_channel_stride = im->nx * im->ny * im->nz; const int multi_channel = im->nc > 1; const int dims[] = {multi_channel ? 4 : 3, im->nx, im->ny, im->nz, multi_channel ? im->nc : 0, 0, 0, 0}; // Initialize a nifti struct and allocate memory if ((nifti = nifti_make_new_nim(dims, DT_FLOAT32, 1)) == NULL) goto write_nii_quit; // Set the slope and intercept to do nothing nifti->scl_slope = 1.0; nifti->scl_inter = 0.0; // Copy the units nifti->dx = im->ux; nifti->dy = im->uy; nifti->dz = im->uz; if (multi_channel) nifti->dt = 0.f; // Channels have no size // Copy the data SIFT3D_IM_LOOP_START_C(im, x, y, z, c) ((float *) nifti->data)[SIFT3D_NIM_GET_IDX(im, x, y, z, c)] = SIFT3D_IM_GET_VOX(im, x, y, z, c); SIFT3D_IM_LOOP_END_C if (nifti_set_filenames(nifti, path, 0, 1)) goto write_nii_quit; // Sanity check if (!nifti_nim_is_valid(nifti, 1)) goto write_nii_quit; nifti_image_write(nifti); nifti_free_extensions(nifti); nifti_image_free(nifti); return SIFT3D_SUCCESS; write_nii_quit: if (nifti != NULL) { nifti_free_extensions(nifti); nifti_image_free(nifti); } return SIFT3D_FAILURE; } #endif ================================================ FILE: imutil/nifti.h ================================================ /* ----------------------------------------------------------------------------- * nifti.h * ----------------------------------------------------------------------------- * Copyright (c) 2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Internal header file for the nifticlib wrapper. * ----------------------------------------------------------------------------- */ #ifndef _NIFTI_H #define _NIFTI_H int read_nii(const char *path, Image *const im); int write_nii(const char *path, const Image *const im); #endif ================================================ FILE: imutil/templates/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for code snippets ################################################################################ install (FILES sep_fir_3d.template DESTINATION ${INSTALL_INCLUDE_DIR}/templates) ================================================ FILE: imutil/templates/sep_fir_3d.template ================================================ /* ----------------------------------------------------------------------------- * sep_fir_3d.template * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains an OpenCL C template for a separable FIR filter. * ----------------------------------------------------------------------------- */ #pragma OPENCL EXTENSION cl_khr_3d_image_writes : enable sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAEREST; kernel void sep_fir_3d(__read_only image3d_t src, __write_only image3d_t dst, const int dx, const int dy, const int dz) { int4 center, d_xyz; int x, y, z; float acc; // Form dimension adjustment d_xyz = (int4) (dx, dy, dz, 0); x = get_global_id(0); y = get_global_id(1); z = get_global_id(2); acc = 0.0f; center = (int4) (x, y, z, 0); ================================================ FILE: reg/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for image registration. ################################################################################ add_library (reg SHARED reg.c) target_link_libraries (reg PUBLIC sift3D imutil) target_link_libraries (reg PRIVATE ${M_LIBRARY}) target_include_directories (reg PUBLIC $ $ ) install (FILES reg.h DESTINATION ${INSTALL_INCLUDE_DIR}) install (TARGETS reg EXPORT SIFT3D-targets RUNTIME DESTINATION ${INSTALL_BIN_DIR} LIBRARY DESTINATION ${INSTALL_LIB_DIR} ARCHIVE DESTINATION ${INSTALL_LIB_DIR}) # If Matlab was found, compile a copy for use with matlab wrappers if (BUILD_Matlab) add_library (mexreg SHARED reg.c) target_link_libraries (mexreg PUBLIC mexsift3D meximutil) target_link_libraries (mexreg PRIVATE ${M_LIBRARY}) target_include_directories (mexreg PUBLIC $ $ ) set_target_properties (mexreg PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} LIBRARY_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} RUNTIME_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} ) install (TARGETS mexreg RUNTIME DESTINATION ${INSTALL_TOOLBOX_DIR} LIBRARY DESTINATION ${INSTALL_TOOLBOX_DIR} ARCHIVE DESTINATION ${INSTALL_TOOLBOX_DIR} ) endif () ================================================ FILE: reg/reg.c ================================================ /* ----------------------------------------------------------------------------- * reg.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains routines for image registration. * ----------------------------------------------------------------------------- */ #include #include #include #include "reg.h" #include "imtypes.h" #include "immacros.h" #include "sift.h" #include "imutil.h" /* Stringify a macro */ #define _SIFT3D_TO_STR(s) #s #define SIFT3D_TO_STR(s) _SIFT3D_TO_STR(s) /* Default parameters */ const double SIFT3D_nn_thresh_default = 0.8; // Default matching threshold /* Internal helper routines */ static void scale_SIFT3D(const double *const factors, SIFT3D_Descriptor_store *const d); static int im2mm(const Mat_rm *const im, const double *const units, Mat_rm *const mm); static int mm2im(const double *const src_units, const double *const ref_units, void *const tform); /* Convert an [mxIM_NDIMS] coordinate matrix from image space to mm. * * Parameters: * im: The input coordinate matrix, in image space. * units: An array of length IM_NDIMS giving the units of image space. * mm: The output coordinate matrix, in mm. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int im2mm(const Mat_rm *const im, const double *const units, Mat_rm *const mm) { int i, j; // Verify inputs if (im->num_cols != IM_NDIMS) { SIFT3D_ERR("im2mm: input must have IM_NDIMS columns. \n"); return SIFT3D_FAILURE; } if (im->type != SIFT3D_DOUBLE) { SIFT3D_ERR("im2mm: input must have type double. \n"); return SIFT3D_FAILURE; } // Copy the input if (copy_Mat_rm(im, mm)) return SIFT3D_FAILURE; // Convert the units SIFT3D_MAT_RM_LOOP_START(mm, i, j) SIFT3D_MAT_RM_GET(mm, i, j, double) *= units[j]; SIFT3D_MAT_RM_LOOP_END return SIFT3D_SUCCESS; } /* Convert a transformation from mm to image space. * * Parameters: * tform: The transformation, which shall be modified. * src_units: The units of the source image, array of length IM_NDIMS. * ref_units: As src_units, but for the reference image. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int mm2im(const double *const src_units, const double *const ref_units, void *const tform) { const tform_type type = tform_get_type(tform); switch (type) { case AFFINE: { int i, j; Affine *const aff = (Affine *const) tform; Mat_rm *const A = &aff->A; // Verify the dimensions if (A->num_rows != IM_NDIMS) { SIFT3D_ERR("mm2im: Invalid transform " "dimensionality: %d \n", A->num_rows); return SIFT3D_FAILURE; } // Convert the Affine transformation matrix in-place SIFT3D_MAT_RM_LOOP_START(A, i, j) // Invert the input transformation ref->mm SIFT3D_MAT_RM_GET(A, i, j, double) *= j < IM_NDIMS ? ref_units[j] : 1.0; // Invert the output transformation src->mm SIFT3D_MAT_RM_GET(A, i, j, double) /= src_units[i]; SIFT3D_MAT_RM_LOOP_END } break; default: SIFT3D_ERR("mm2im: unsupported transform type. \n"); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Initialize a Reg_SIFT3D struct with the default parameters. This must be * called before the struct can be used. */ int init_Reg_SIFT3D(Reg_SIFT3D *const reg) { reg->nn_thresh = SIFT3D_nn_thresh_default; init_SIFT3D_Descriptor_store(®->desc_src); init_SIFT3D_Descriptor_store(®->desc_ref); init_Ransac(®->ran); if (init_SIFT3D(®->sift3d) || init_Mat_rm(®->match_src, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(®->match_ref, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) { SIFT3D_ERR("register_SIFT3D: unexpected error \n"); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Free all memory associated with a Reg_SIFT3D struct. reg cannot be reused * unless it is reinitialized. */ void cleanup_Reg_SIFT3D(Reg_SIFT3D *const reg) { cleanup_SIFT3D_Descriptor_store(®->desc_src); cleanup_SIFT3D_Descriptor_store(®->desc_ref); cleanup_SIFT3D(®->sift3d); cleanup_Mat_rm(®->match_src); cleanup_Mat_rm(®->match_ref); } /* Set the matching theshold of a Reg_SIFT3D struct. */ int set_nn_thresh_Reg_SIFT3D(Reg_SIFT3D *const reg, const double nn_thresh) { if (nn_thresh <= 0 || nn_thresh > 1) { SIFT3D_ERR("set_nn_thresh_Reg_SIFT3D: invalid threshold: " "%f \n", nn_thresh); return SIFT3D_FAILURE; } reg->nn_thresh = nn_thresh; return SIFT3D_SUCCESS; } /* Set the Ransac parameters of the Reg_SIFT3D struct. */ int set_Ransac_Reg_SIFT3D(Reg_SIFT3D *const reg, const Ransac *const ran) { return copy_Ransac(ran, ®->ran); } /* Set the SIFT3D parameters of the Reg_SIFT3D struct. Makes a deep copy of * sift3d, so you are free to modify it after calling this function. */ int set_SIFT3D_Reg_SIFT3D(Reg_SIFT3D *const reg, const SIFT3D *const sift3d) { return copy_SIFT3D(sift3d, ®->sift3d); } /* Helper function for set_src_Reg_SIFT3D and set_ref_Reg_SIFT3D. * * Parameters: * reg - The Reg_SIFT3D struct. * im - The image, either source or reference. * units - The units array in Reg_SIFT3D to be modified. * desc - The descriptor store in Reg_SIFT3D to be modified. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int set_im_Reg_SIFT3D(Reg_SIFT3D *const reg, const Image *const im, double *const units, SIFT3D_Descriptor_store *const desc) { Keypoint_store kp; SIFT3D *const sift3d = ®->sift3d; /* Initialize intermediates */ init_Keypoint_store(&kp); /* Save the units */ memcpy(units, SIFT3D_IM_GET_UNITS(im), IM_NDIMS * sizeof(double)); /* Detect keypoints */ if (SIFT3D_detect_keypoints(sift3d, im, &kp)) { SIFT3D_ERR("set_" SIFT3D_TO_STR(type) "_Reg_SIFT3D: failed to detect keypoints\n"); goto set_im_quit; } /* Extract descriptors */ if (SIFT3D_extract_descriptors(sift3d, &kp, desc)) { SIFT3D_ERR("set_" SIFT3D_TO_STR(type) "_Reg_SIFT3D: failed to extract descriptors \n"); goto set_im_quit; } /* Clean up */ cleanup_Keypoint_store(&kp); return SIFT3D_SUCCESS; set_im_quit: cleanup_Keypoint_store(&kp); return SIFT3D_FAILURE; } /* Set the source image. This makes a deep copy of the data, so you are free * to modify src after calling this function. */ int set_src_Reg_SIFT3D(Reg_SIFT3D *const reg, const Image *const src) { return set_im_Reg_SIFT3D(reg, src, reg->src_units, ®->desc_src); } /* The same as set_source_Reg_SIFT3D, but sets the reference image. */ int set_ref_Reg_SIFT3D(Reg_SIFT3D *const reg, const Image *const ref) { return set_im_Reg_SIFT3D(reg, ref, reg->ref_units, ®->desc_ref); } /* Run the registration procedure. * * Parameters: * reg: The struct holding registration state. * tform: The output transformation. If NULL, this function only performs * feature matching. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int register_SIFT3D(Reg_SIFT3D *const reg, void *const tform) { Mat_rm match_src_mm, match_ref_mm; int *matches; int i, j; Ransac *const ran = ®->ran; Mat_rm *const match_src = ®->match_src; Mat_rm *const match_ref = ®->match_ref; const double nn_thresh = reg->nn_thresh; SIFT3D_Descriptor_store *const desc_src = ®->desc_src; SIFT3D_Descriptor_store *const desc_ref = ®->desc_ref; // Verify inputs if (desc_src->num <= 0) { SIFT3D_ERR("register_SIFT3D: no source image descriptors " "are available \n"); return SIFT3D_FAILURE; } if (desc_ref->num <= 0) { SIFT3D_ERR("register_SIFT3D: no reference image " "descriptors are available \n"); return SIFT3D_FAILURE; } // Initialize intermediates matches = NULL; if (init_Mat_rm(&match_src_mm, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&match_ref_mm, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) { SIFT3D_ERR("register_SIFT3D: failed initialization \n"); return SIFT3D_FAILURE; } // Match features if (SIFT3D_nn_match(desc_src, desc_ref, nn_thresh, &matches)) { SIFT3D_ERR("register_SIFT3D: failed to match " "descriptors \n"); goto register_SIFT3D_quit; } // Convert matches to coordinate matrices if (SIFT3D_matches_to_Mat_rm(desc_src, desc_ref, matches, match_src, match_ref)) { SIFT3D_ERR("register_SIFT3D: failed to extract " "coordinate matrices \n"); goto register_SIFT3D_quit; } // Quit if no tform was provided if (tform == NULL) goto register_SIFT3D_success; // Convert the coordinate matrices to real-world units if (im2mm(match_src, reg->src_units, &match_src_mm) || im2mm(match_ref, reg->ref_units, &match_ref_mm)) goto register_SIFT3D_quit; // Find the transformation in real-world units if (find_tform_ransac(ran, &match_src_mm, &match_ref_mm, tform)) goto register_SIFT3D_quit; // Convert the transformation back to image space if (mm2im(reg->src_units, reg->ref_units, tform)) goto register_SIFT3D_quit; register_SIFT3D_success: // Clean up free(matches); cleanup_Mat_rm(&match_src_mm); cleanup_Mat_rm(&match_ref_mm); return SIFT3D_SUCCESS; register_SIFT3D_quit: free(matches); cleanup_Mat_rm(&match_src_mm); cleanup_Mat_rm(&match_ref_mm); return SIFT3D_FAILURE; } /* Helper function to scale the descriptors by the given factors */ static void scale_SIFT3D(const double *const factors, SIFT3D_Descriptor_store *const d) { double det, scale_factor; int i, j, k; // Compute the determinant of the scaling transformation det = 1.0; for (i = 0; i < IM_NDIMS; i++) { det *= factors[i]; } // Compute the scale parameter factor from the determinant scale_factor = pow(det, -1.0 / (double) IM_NDIMS); // Scale the descriptors for (i = 0; i < d->num; i++) { SIFT3D_Descriptor *const desc = d->buf + i; // Scale the coordinates desc->xd *= factors[0]; desc->yd *= factors[1]; desc->zd *= factors[2]; // Adjust the scale parameter desc->sd *= scale_factor; } } /* Like register_SIFT3D, but resamples the input images to have the same * physical resolution before extracting features. Use this when registering * images with very different resolutions. The results are converted to the * original resolution. * * Parameters: * reg: See register_SIFT3D. * src: The source, or moving image. * ref: The reference, or fixed image. * interp: The type of interpolation to use. * tform: See register_SIFT3D. * * Note that some fields of the returned keypoints, such as scale and * orientation, will not make sense in the new coordinate system. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int register_SIFT3D_resample(Reg_SIFT3D *const reg, const Image *const src, const Image *const ref, const interp_type interp, void *const tform) { double units_min[IM_NDIMS], factors_src[IM_NDIMS], factors_ref[IM_NDIMS]; Image src_interp, ref_interp; int i; // Check for the trivial case, when src and dst have the same units if (!memcmp(SIFT3D_IM_GET_UNITS(src), SIFT3D_IM_GET_UNITS(ref), IM_NDIMS * sizeof(double))) { return set_src_Reg_SIFT3D(reg, src) || set_ref_Reg_SIFT3D(reg, ref) || register_SIFT3D(reg, tform) ? SIFT3D_FAILURE : SIFT3D_SUCCESS; } // Initalize intermediates init_im(&src_interp); init_im(&ref_interp); // Compute the new units and scaling factors for (i = 0; i < IM_NDIMS; i++) { const double unit_src = SIFT3D_IM_GET_UNITS(src)[i]; const double unit_ref = SIFT3D_IM_GET_UNITS(ref)[i]; // Compute the minimum units between the two images units_min[i] = SIFT3D_MIN(unit_src, unit_ref); // Compute the scaling factors between the interpolated // images and the originals factors_src[i] = units_min[i] / unit_src; factors_ref[i] = units_min[i] / unit_ref; } // Resample the images if (im_resample(src, units_min, interp, &src_interp) || im_resample(ref, units_min, interp, &ref_interp)) goto register_interp_quit; // Extract features from the interpolated images if (set_src_Reg_SIFT3D(reg, &src_interp) || set_ref_Reg_SIFT3D(reg, &ref_interp)) goto register_interp_quit; // Convert the keypoints and descriptors to the original units scale_SIFT3D(factors_src, ®->desc_src); scale_SIFT3D(factors_ref, ®->desc_ref); // Register the images if (register_SIFT3D(reg, tform)) goto register_interp_quit; // Clean up im_free(&src_interp); im_free(&ref_interp); return SIFT3D_SUCCESS; register_interp_quit: im_free(&src_interp); im_free(&ref_interp); return SIFT3D_FAILURE; } /* Write the coordinates of matching keypoints to the matrices match_src * and match_ref. This function uses the keypoints and the matches from * the last call to register_SIFT3D() on this Reg_SIFT3D struct. */ int get_matches_Reg_SIFT3D(const Reg_SIFT3D *const reg, Mat_rm *const match_src, Mat_rm *const match_ref) { // Copy the matches return copy_Mat_rm(®->match_src, match_src) || copy_Mat_rm(®->match_ref, match_ref); } ================================================ FILE: reg/reg.h ================================================ /* ----------------------------------------------------------------------------- * reg.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Public header file for reg.c. * ----------------------------------------------------------------------------- */ #include "imtypes.h" #ifndef _REG_H #define _REG_H #ifdef __cplusplus extern "C" { #endif /* Parameters */ const extern double SIFT3D_nn_thresh_default; // Default matching threshold /* Internal data for the SIFT3D + RANSAC registration process */ typedef struct _Reg_SIFT3D { double src_units[IM_NDIMS], ref_units[IM_NDIMS]; SIFT3D sift3d; Ransac ran; SIFT3D_Descriptor_store desc_src, desc_ref; Mat_rm match_src, match_ref; double nn_thresh; int verbose; } Reg_SIFT3D; int init_Reg_SIFT3D(Reg_SIFT3D *const reg); void cleanup_Reg_SIFT3D(Reg_SIFT3D *const reg); int register_SIFT3D(Reg_SIFT3D *const reg, void *const tform); int register_SIFT3D_resample(Reg_SIFT3D *const reg, const Image *const src, const Image *const ref, const interp_type interp, void *const tform); int set_nn_thresh_Reg_SIFT3D(Reg_SIFT3D *const reg, const double nn_thresh); int set_Ransac_Reg_SIFT3D(Reg_SIFT3D *const reg, const Ransac *const ran); int set_SIFT3D_Reg_SIFT3D(Reg_SIFT3D *const reg, const SIFT3D *const sift3d); int set_src_Reg_SIFT3D(Reg_SIFT3D *const reg, const Image *const src); int set_ref_Reg_SIFT3D(Reg_SIFT3D *const reg, const Image *const ref); int get_matches_Reg_SIFT3D(const Reg_SIFT3D *const reg, Mat_rm *const match_src, Mat_rm *const match_ref); #ifdef __cplusplus } #endif #endif ================================================ FILE: sift3d/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for 3D SIFT keypoint detection, description, and matching. ################################################################################ add_library (sift3D SHARED sift.c) target_link_libraries (sift3D PUBLIC imutil) target_link_libraries (sift3D PRIVATE ${M_LIBRARY}) target_include_directories (sift3D PUBLIC $ $ ) install (FILES sift.h DESTINATION ${INSTALL_INCLUDE_DIR}) # If Matlab was found, compile a copy for use with matlab wrappers if (BUILD_Matlab) add_library (mexsift3D SHARED sift.c) target_include_directories (mexsift3D PUBLIC $ $ ) target_link_libraries (mexsift3D PUBLIC meximutil) target_link_libraries (mexsift3D PRIVATE ${M_LIBRARY}) set_target_properties (mexsift3D PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} LIBRARY_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} RUNTIME_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} ) install (TARGETS mexsift3D RUNTIME DESTINATION ${INSTALL_TOOLBOX_DIR} LIBRARY DESTINATION ${INSTALL_TOOLBOX_DIR} ARCHIVE DESTINATION ${INSTALL_TOOLBOX_DIR} ) endif () install (TARGETS sift3D EXPORT SIFT3D-targets RUNTIME DESTINATION ${INSTALL_BIN_DIR} LIBRARY DESTINATION ${INSTALL_LIB_DIR} ARCHIVE DESTINATION ${INSTALL_LIB_DIR}) ================================================ FILE: sift3d/sift.c ================================================ /* ----------------------------------------------------------------------------- * sift.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * This file contains all routines needed to initialize, delete, * and run the SIFT3D detector and descriptor. It also contains routines for * matching SIFT3D features and drawing the results. * ----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include "imtypes.h" #include "immacros.h" #include "imutil.h" #include "sift.h" /* Implementation options */ //#define SIFT3D_ORI_SOLID_ANGLE_WEIGHT // Weight bins by solid angle //#define SIFT3D_MATCH_MAX_DIST 0.3 // Maximum distance between matching features //#define CUBOID_EXTREMA // Search for extrema in a cuboid region /* Internal return codes */ #define REJECT 1 /* Default SIFT3D parameters. These may be overriden by * the calling appropriate functions. */ const double peak_thresh_default = 0.1; // DoG peak threshold const int num_kp_levels_default = 3; // Number of levels per octave in which keypoints are found const double corner_thresh_default = 0.4; // Minimum corner score const double sigma_n_default = 1.15; // Nominal scale of input data const double sigma0_default = 1.6; // Scale of the base octave /* SIFT3D option names */ const char opt_peak_thresh[] = "peak_thresh"; const char opt_corner_thresh[] = "corner_thresh"; const char opt_num_kp_levels[] = "num_kp_levels"; const char opt_sigma_n[] = "sigma_n"; const char opt_sigma0[] = "sigma0"; /* Internal parameters */ const double max_eig_ratio = 0.90; // Maximum ratio of eigenvalue magnitudes const double ori_grad_thresh = 1E-10; // Minimum norm of average gradient const double bary_eps = FLT_EPSILON * 1E1; // Error tolerance for barycentric coordinates const double ori_sig_fctr = 1.5; // Ratio of window parameter to keypoint scale const double ori_rad_fctr = 3.0; // Ratio of window radius to parameter const double desc_sig_fctr = 7.071067812; // See ori_sig_fctr, 5 * sqrt(2) const double desc_rad_fctr = 2.0; // See ori_rad_fctr const double trunc_thresh = 0.2f * 128.0f / DESC_NUMEL; // Descriptor truncation threshold /* Internal math constants */ const double gr = 1.6180339887; // Golden ratio /* Get the index of bin j from triangle i */ #define MESH_GET_IDX(mesh, i, j) \ ((mesh)->tri[i].idx[j]) /* Get bin j from triangle i */ #define MESH_HIST_GET(mesh, hist, i, j) \ ((hist)->bins[MESH_GET_IDX(mesh, i, j)]) /* Clamp out of bounds polar accesses to the first or last element. * Note that the polar histogram is NOT circular. */ #define HIST_GET_PO(hist, a, p) \ ((p) < 0 ? \ HIST_GET(hist, ((a) + NBINS_AZ / 2) % NBINS_AZ, 1) : \ (p) >= NBINS_PO ? \ HIST_GET(hist, ((a) + NBINS_AZ / 2) % NBINS_AZ, \ NBINS_PO - 1) : \ HIST_GET(hist, a, p)) /* Convert out of bounds azimuthal accesses circularly, e.g. -1 goes * to NBINS_AZ - 1, NBINS_AZ goes to 0. This algorithm does not work * if the indices wrap around more than once. */ #define HIST_GET_AZ(hist, a, p) \ HIST_GET_PO(hist, ((a) + NBINS_AZ) % NBINS_AZ, p) /* Loop through a spherical image region. im and [x, y, z] are defined as * above. vcenter is a pointer to a Cvec specifying the center of the window. * rad is the radius of the window. vdisp is a pointer to a Cvec storing * the displacement from the window center. sqdisp is a float storing the * squared Euclidean distance from the window center. * * Note that the sphere is defined in real-world coordinates, i.e. those * with units (1, 1, 1). Thus, rad, sq_dist, and vdisp are defined in these * coordinates as well. However, x, y, z, and vcenter are defined in image * space. * * Delimit with IM_LOOP_SPHERE_END. */ #define IM_LOOP_SPHERE_START(im, x, y, z, vcenter, rad, vdisp, sq_dist) \ { \ const float uxf = (float) (im)->ux; \ const float uyf = (float) (im)->uy; \ const float uzf = (float) (im)->uz; \ const int x_start = SIFT3D_MAX(floorf((vcenter)->x - (rad) / uxf), 1); \ const int x_end = SIFT3D_MIN(ceilf((vcenter)->x + (rad) / uxf), \ im->nx - 2); \ const int y_start = SIFT3D_MAX(floorf((vcenter)->y - (rad) / uyf), 1); \ const int y_end = SIFT3D_MIN(ceilf((vcenter)->y + (rad) / uyf), \ im->ny - 2); \ const int z_start = SIFT3D_MAX(floorf((vcenter)->z - (rad) / uzf), 1); \ const int z_end = SIFT3D_MIN(ceilf((vcenter)->z + (rad) / uzf), \ im->nz - 2); \ SIFT3D_IM_LOOP_LIMITED_START(im, x, y, z, x_start, x_end, y_start, \ y_end, z_start, z_end) \ (vdisp)->x = ((float) x - (vcenter)->x) * uxf; \ (vdisp)->y = ((float) y - (vcenter)->y) * uyf; \ (vdisp)->z = ((float) z - (vcenter)->z) * uzf; \ (sq_dist) = SIFT3D_CVEC_L2_NORM_SQ(vdisp); \ if ((sq_dist) > (rad) * (rad)) \ continue; \ #define IM_LOOP_SPHERE_END SIFT3D_IM_LOOP_END } // Loop over all bins in a gradient histogram. If ICOS_HIST is defined, p // is not referenced #ifdef ICOS_HIST #define HIST_LOOP_START(a, p) \ for ((a) = 0; (a) < HIST_NUMEL; (a)++) { p = p; { #else #define HIST_LOOP_START(a, p) \ for ((p) = 0; (p) < NBINS_PO; (p)++) { \ for ((a) = 0; (a) < NBINS_AZ; (a)++) { #endif // Delimit a HIST_LOOP #define HIST_LOOP_END }} // Get an element from a gradient histogram. If ICOS_HIST is defined, p // is not referenced #ifdef ICOS_HIST #define HIST_GET_IDX(a, p) (a) #else #define HIST_GET_IDX(a, p) ((a) + (p) * NBINS_AZ) #endif #define HIST_GET(hist, a, p) ((hist)->bins[HIST_GET_IDX(a, p)]) // Get a column index in the matrix representation of a // SIFT3D_Descriptor_store struct #define DESC_MAT_GET_COL(hist_idx, a, p) \ (((hist_idx) * HIST_NUMEL) + HIST_GET_IDX(a, p) + IM_NDIMS) // As SIFT3D_IM_GET_GRAD, but with physical units (1, 1, 1) #define IM_GET_GRAD_ISO(im, x, y, z, c, vd) { \ SIFT3D_IM_GET_GRAD(im, x, y, z, c, vd); \ (vd)->x *= 1.0f / (float) (im)->ux; \ (vd)->y *= 1.0f / (float) (im)->uy; \ (vd)->z *= 1.0f / (float) (im)->uz; \ } /* Global variables */ extern CL_data cl_data; /* Helper routines */ static int init_geometry(SIFT3D *sift3d); static int set_im_SIFT3D(SIFT3D *const sift3d, const Image *const im); static int set_scales_SIFT3D(SIFT3D *const sift3d, const double sigma0, const double sigma_n); static int resize_SIFT3D(SIFT3D *const sift3d, const int num_kp_levels); static int build_gpyr(SIFT3D *sift3d); static int build_dog(SIFT3D *dog); static int detect_extrema(SIFT3D *sift3d, Keypoint_store *kp); static int assign_orientations(SIFT3D *const sift3d, Keypoint_store *const kp); static int assign_orientation_thresh(const Image *const im, const Cvec *const vcenter, const double sigma, const double thresh, Mat_rm *const R); static int assign_eig_ori(const Image *const im, const Cvec *const vcenter, const double sigma, Mat_rm *const R, double *const conf); static int Cvec_to_sbins(const Cvec * const vd, Svec * const bins); static void refine_Hist(Hist *hist); static int init_cl_SIFT3D(SIFT3D *sift3d); static int cart2bary(const Cvec * const cart, const Tri * const tri, Cvec * const bary, float * const k); static int scale_Keypoint(const Keypoint *const src, const double *const factors, Keypoint *const dst); static int smooth_scale_raw_input(const SIFT3D *const sift3d, const Image *const src, Image *const dst); static int verify_keys(const Keypoint_store *const kp, const Image *const im); static int keypoint2base(const Keypoint *const src, Keypoint *const dst); static int _SIFT3D_extract_descriptors(SIFT3D *const sift3d, const Pyramid *const gpyr, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc); static void SIFT3D_desc_acc_interp(const SIFT3D * const sift3d, const Cvec * const vbins, const Cvec * const grad, SIFT3D_Descriptor * const desc); static int extract_descrip(SIFT3D *const sift3d, const Image *const im, const Keypoint *const key, SIFT3D_Descriptor *const desc); static int argv_remove(const int argc, char **argv, const unsigned char *processed); static int extract_dense_descriptors_no_rotate(SIFT3D *const sift3d, const Image *const in, Image *const desc); static int extract_dense_descriptors_rotate(SIFT3D *const sift3d, const Image *const in, Image *const desc); static int extract_dense_descrip_rotate(SIFT3D *const sift3d, const Image *const im, const Cvec *const vcenter, const double sigma, const Mat_rm *const R, Hist *const hist); static void vox2hist(const Image *const im, const int x, const int y, const int z, Hist *const hist); static void hist2vox(Hist *const hist, const Image *const im, const int x, const int y, const int z); static int match_desc(const SIFT3D_Descriptor *const desc, const SIFT3D_Descriptor_store *const store, const float nn_thresh); static int resize_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc, const int num); /* Initialize geometry tables. */ static int init_geometry(SIFT3D *sift3d) { Mat_rm V, F; Cvec temp1, temp2, temp3, n; float mag; int i, j; Mesh * const mesh = &sift3d->mesh; /* Verices of a regular icosahedron inscribed in the unit sphere. */ const float vert[] = { 0, 1, gr, 0, -1, gr, 0, 1, -gr, 0, -1, -gr, 1, gr, 0, -1, gr, 0, 1, -gr, 0, -1, -gr, 0, gr, 0, 1, -gr, 0, 1, gr, 0, -1, -gr, 0, -1 }; /* Vertex triplets forming the faces of the icosahedron. */ const float faces[] = {0, 1, 8, 0, 8, 4, 0, 4, 5, 0, 5, 9, 0, 9, 1, 1, 6, 8, 8, 6, 10, 8, 10, 4, 4, 10, 2, 4, 2, 5, 5, 2, 11, 5, 11, 9, 9, 11, 7, 9, 7, 1, 1, 7, 6, 3, 6, 7, 3, 7, 11, 3, 11, 2, 3, 2, 10, 3, 10, 6}; // Initialize matrices if (init_Mat_rm_p(&V, vert, ICOS_NVERT, 3, SIFT3D_FLOAT, SIFT3D_FALSE) || init_Mat_rm_p(&F, faces, ICOS_NFACES, 3, SIFT3D_FLOAT, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Initialize triangle memory init_Mesh(mesh); if ((mesh->tri = (Tri *) SIFT3D_safe_realloc(mesh->tri, ICOS_NFACES * sizeof(Tri))) == NULL) return SIFT3D_FAILURE; // Populate the triangle struct for each face for (i = 0; i < ICOS_NFACES; i++) { Tri * const tri = mesh->tri + i; Cvec * const v = tri->v; // Initialize the vertices for (j = 0; j < 3; j++) { const float mag_expected = sqrt(1 + gr * gr); int * const idx = tri->idx + j; *idx = SIFT3D_MAT_RM_GET(&F, i, j, float); // Initialize the vector v[j].x = SIFT3D_MAT_RM_GET(&V, *idx, 0, float); v[j].y = SIFT3D_MAT_RM_GET(&V, *idx, 1, float); v[j].z = SIFT3D_MAT_RM_GET(&V, *idx, 2, float); // Normalize to unit length mag = SIFT3D_CVEC_L2_NORM(v + j); assert(fabsf(mag - mag_expected) < 1E-10); SIFT3D_CVEC_SCALE(v + j, 1.0f / mag); } // Compute the normal vector at v[0] as (V2 - V1) X (V1 - V0) SIFT3D_CVEC_OP(v + 2, v + 1, -, &temp1); SIFT3D_CVEC_OP(v + 1, v, -, &temp2); SIFT3D_CVEC_CROSS(&temp1, &temp2, &n); // Ensure this vector is facing outward from the origin if (SIFT3D_CVEC_DOT(&n, v) < 0) { // Swap two vertices temp1 = v[0]; v[0] = v[1]; v[1] = temp1; // Compute the normal again SIFT3D_CVEC_OP(v + 2, v + 1, -, &temp1); SIFT3D_CVEC_OP(v + 1, v, -, &temp2); SIFT3D_CVEC_CROSS(&temp1, &temp2, &n); } assert(SIFT3D_CVEC_DOT(&n, v) >= 0); // Ensure the triangle is equilateral SIFT3D_CVEC_OP(v + 2, v, -, &temp3); assert(fabsf(SIFT3D_CVEC_L2_NORM(&temp1) - SIFT3D_CVEC_L2_NORM(&temp2)) < 1E-10); assert(fabsf(SIFT3D_CVEC_L2_NORM(&temp1) - SIFT3D_CVEC_L2_NORM(&temp3)) < 1E-10); } return SIFT3D_SUCCESS; } /* Convert Cartesian coordinates to barycentric. bary is set to all zeros if * the problem is unstable. * * The output value k is the constant by which the ray is multiplied to * intersect the supporting plane of the triangle. * * This code uses the Moller-Trumbore algorithm. */ static int cart2bary(const Cvec * const cart, const Tri * const tri, Cvec * const bary, float * const k) { Cvec e1, e2, t, p, q; float det, det_inv; const Cvec * const v = tri->v; SIFT3D_CVEC_OP(v + 1, v, -, &e1); SIFT3D_CVEC_OP(v + 2, v, -, &e2); SIFT3D_CVEC_CROSS(cart, &e2, &p); det = SIFT3D_CVEC_DOT(&e1, &p); // Reject unstable points if (fabsf(det) < bary_eps) { return SIFT3D_FAILURE; } det_inv = 1.0f / det; t = v[0]; SIFT3D_CVEC_SCALE(&t, -1.0f); SIFT3D_CVEC_CROSS(&t, &e1, &q); bary->y = det_inv * SIFT3D_CVEC_DOT(&t, &p); bary->z = det_inv * SIFT3D_CVEC_DOT(cart, &q); bary->x = 1.0f - bary->y - bary->z; *k = SIFT3D_CVEC_DOT(&e2, &q) * det_inv; #ifndef NDEBUG Cvec temp1, temp2, temp3; double residual; if (isnan(bary->x) || isnan(bary->y) || isnan(bary->z)) { printf("cart2bary: invalid bary (%f, %f, %f)\n", bary->x, bary->y, bary->z); //exit(1); } // Verify k * c = bary->x * v1 + bary->y * v2 + bary->z * v3 temp1 = v[0]; temp2 = v[1]; temp3 = v[2]; SIFT3D_CVEC_SCALE(&temp1, bary->x); SIFT3D_CVEC_SCALE(&temp2, bary->y); SIFT3D_CVEC_SCALE(&temp3, bary->z); SIFT3D_CVEC_OP(&temp1, &temp2, +, &temp1); SIFT3D_CVEC_OP(&temp1, &temp3, +, &temp1); SIFT3D_CVEC_SCALE(&temp1, 1.0f / *k); SIFT3D_CVEC_OP(&temp1, cart, -, &temp1); residual = SIFT3D_CVEC_L2_NORM(&temp1); if (residual > bary_eps) { printf("cart2bary: residual: %f\n", residual); exit(1); } #endif return SIFT3D_SUCCESS; } /* Initialize a Keypoint_store for first use. * This does not need to be called to reuse the store * for a new image. */ void init_Keypoint_store(Keypoint_store *const kp) { init_Slab(&kp->slab); kp->buf = (Keypoint *) kp->slab.buf; } /* Initialize a Keypoint struct for use. This sets up the internal pointers, * and nothing else. If called on a valid Keypoint struct, it has no effect. */ int init_Keypoint(Keypoint *const key) { // Initialize the orientation matrix with static memory return init_Mat_rm_p(&key->R, key->r_data, IM_NDIMS, IM_NDIMS, SIFT3D_FLOAT, SIFT3D_FALSE); } /* Make room for at least num Keypoint structs in kp. * * Note: This function must re-initialize some internal data if it was moved. * This does not affect the end user, but it affects the implementation of * init_Keypoint. */ int resize_Keypoint_store(Keypoint_store *const kp, const size_t num) { void *const buf_old = kp->slab.buf; // Resize the internal memory SIFT3D_RESIZE_SLAB(&kp->slab, num, sizeof(struct _Keypoint)); kp->buf = kp->slab.buf; // If the size has changed, re-initialize the keypoints if (buf_old != kp->slab.buf) { int i; for (i = 0; i < kp->slab.num; i++) { Keypoint *const key = kp->buf + i; if (init_Keypoint(key)) return SIFT3D_FAILURE; } } return SIFT3D_SUCCESS; } /* Copy one Keypoint struct into another. */ int copy_Keypoint(const Keypoint *const src, Keypoint *const dst) { // Copy the shallow data dst->xd = src->xd; dst->yd = src->yd; dst->zd = src->zd; dst->sd = src->sd; dst->o = src->o; dst->s = src->s; // Copy the orienation matrix return copy_Mat_rm(&src->R, &dst->R); } /* Free all memory associated with a Keypoint_store. kp cannot be * used after calling this function, unless re-initialized. */ void cleanup_Keypoint_store(Keypoint_store *const kp) { cleanup_Slab(&kp->slab); } /* Initialize a SIFT_Descriptor_store for first use. * This does not need to be called to reuse the store * for a new image. */ void init_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc) { desc->buf = NULL; } /* Free all memory associated with a SIFT3D_Descriptor_store. desc * cannot be used after calling this function, unless re-initialized. */ void cleanup_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc) { free(desc->buf); } /* Resize a SIFT3D_Descriptor_store to hold n descriptors. Must be initialized * prior to calling this function. num must be positive. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int resize_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc, const int num) { if (num < 1) { SIFT3D_ERR("resize_SIFT3D_Descriptor_store: invalid size: %d", num); return SIFT3D_FAILURE; } if ((desc->buf = (SIFT3D_Descriptor *) SIFT3D_safe_realloc(desc->buf, num * sizeof(SIFT3D_Descriptor))) == NULL) return SIFT3D_FAILURE; desc->num = num; return SIFT3D_SUCCESS; } /* Initializes the OpenCL data for this SIFT3D struct. This * increments the reference counts for shared data. */ static int init_cl_SIFT3D(SIFT3D *sift3d) { #ifdef SIFT3D_USE_OPENCL cl_image_format image_format; // Initialize basic OpenCL platform and context info image_format.image_channel_order = CL_R; image_format.image_channel_data_type = CL_FLOAT; if (init_cl(&cl_data, PLATFORM_NAME_NVIDIA, CL_DEVICE_TYPE_GPU, CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR, image_format)) return SIFT3D_FAILURE; // Load and compile the downsampling kernel #endif return SIFT3D_SUCCESS; } /* Sets the peak threshold, checking that it is in the interval (0, inf) */ int set_peak_thresh_SIFT3D(SIFT3D *const sift3d, const double peak_thresh) { if (peak_thresh <= 0.0 || peak_thresh > 1) { SIFT3D_ERR("SIFT3D peak_thresh must be in the interval (0, 1]. " "Provided: %f \n", peak_thresh); return SIFT3D_FAILURE; } sift3d->peak_thresh = peak_thresh; return SIFT3D_SUCCESS; } /* Sets the corner threshold, checking that it is in the interval [0, 1]. */ int set_corner_thresh_SIFT3D(SIFT3D *const sift3d, const double corner_thresh) { if (corner_thresh < 0.0 || corner_thresh > 1.0) { SIFT3D_ERR("SIFT3D corner_thresh must be in the interval " "[0, 1]. Provided: %f \n", corner_thresh); return SIFT3D_FAILURE; } sift3d->corner_thresh = corner_thresh; return SIFT3D_SUCCESS; } /* Sets the number of levels per octave. This function will resize the * internal data. */ int set_num_kp_levels_SIFT3D(SIFT3D *const sift3d, const unsigned int num_kp_levels) { const Pyramid *const gpyr = &sift3d->gpyr; return resize_SIFT3D(sift3d, num_kp_levels); } /* Sets the nominal scale parameter of the input data, checking that it is * nonnegative. */ int set_sigma_n_SIFT3D(SIFT3D *const sift3d, const double sigma_n) { const double sigma0 = sift3d->gpyr.sigma0; if (sigma_n < 0.0) { SIFT3D_ERR("SIFT3D sigma_n must be nonnegative. Provided: " "%f \n", sigma_n); return SIFT3D_FAILURE; } return set_scales_SIFT3D(sift3d, sigma0, sigma_n); } /* Sets the scale parameter of the first level of octave 0, checking that it * is nonnegative. */ int set_sigma0_SIFT3D(SIFT3D *const sift3d, const double sigma0) { const double sigma_n = sift3d->gpyr.sigma_n; if (sigma0 < 0.0) { SIFT3D_ERR("SIFT3D sigma0 must be nonnegative. Provided: " "%f \n", sigma0); return SIFT3D_FAILURE; } return set_scales_SIFT3D(sift3d, sigma0, sigma_n); } /* Initialize a SIFT3D struct with the default parameters. */ int init_SIFT3D(SIFT3D *sift3d) { Pyramid *const dog = &sift3d->dog; Pyramid *const gpyr = &sift3d->gpyr; GSS_filters *const gss = &sift3d->gss; // Initialize to defaults const double peak_thresh = peak_thresh_default; const double corner_thresh = corner_thresh_default; const int num_kp_levels = num_kp_levels_default; const double sigma_n = sigma_n_default; const double sigma0 = sigma0_default; const int dense_rotate = SIFT3D_FALSE; // First-time pyramid initialization init_Pyramid(dog); init_Pyramid(gpyr); // First-time filter initialization init_GSS_filters(gss); // Intialize the geometry tables if (init_geometry(sift3d)) return SIFT3D_FAILURE; // init static OpenCL programs and contexts, if support is enabled if (init_cl_SIFT3D(sift3d)) return SIFT3D_FAILURE; // Initialize the image data init_im(&sift3d->im); // Save data dog->first_level = gpyr->first_level = -1; sift3d->dense_rotate = dense_rotate; if (set_sigma_n_SIFT3D(sift3d, sigma_n) || set_sigma0_SIFT3D(sift3d, sigma0) || set_peak_thresh_SIFT3D(sift3d, peak_thresh) || set_corner_thresh_SIFT3D(sift3d, corner_thresh) || set_num_kp_levels_SIFT3D(sift3d, num_kp_levels)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Make a deep copy of a SIFT3D struct, including all internal images. */ int copy_SIFT3D(const SIFT3D *const src, SIFT3D *const dst) { // Free and re-initialize dst cleanup_SIFT3D(dst); if (init_SIFT3D(dst)) return SIFT3D_FAILURE; // Copy the parameters set_sigma_n_SIFT3D(dst, src->gpyr.sigma_n); set_sigma0_SIFT3D(dst, src->gpyr.sigma0); if (set_peak_thresh_SIFT3D(dst, src->peak_thresh) || set_corner_thresh_SIFT3D(dst, src->corner_thresh) || set_num_kp_levels_SIFT3D(dst, src->gpyr.num_kp_levels)) return SIFT3D_FAILURE; dst->dense_rotate = src->dense_rotate; // Copy the image, if any if (src->im.data != NULL && set_im_SIFT3D(dst, &src->im)) return SIFT3D_FAILURE; // Copy the pyramids, if any if (copy_Pyramid(&src->gpyr, &dst->gpyr) || copy_Pyramid(&src->dog, &dst->dog)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Free all memory associated with a SIFT3D struct. sift3d cannot be reused * unless it is reinitialized. */ void cleanup_SIFT3D(SIFT3D *const sift3d) { // Clean up the image copy im_free(&sift3d->im); // Clean up the pyramids cleanup_Pyramid(&sift3d->gpyr); cleanup_Pyramid(&sift3d->dog); // Clean up the GSS filters cleanup_GSS_filters(&sift3d->gss); // Clean up the triangle mesh cleanup_Mesh(&sift3d->mesh); #ifdef USE_OPENCL // Clean up the OpenCL kernels cleanup_SIFT3D_cl_kernels(&sift3d->kernels); #endif } /* Helper function to remove the processed arguments from argv. * Returns the number of remaining arguments. */ static int argv_remove(const int argc, char **argv, const unsigned char *processed) { int i, new_pos; // Remove the processed arguments in-place new_pos = 0; for (i = 0; i < argc; i++) { // Skip processed arguments if (processed[i]) continue; // Add the unprocessed arguments to the new argv argv[new_pos++] = argv[i]; } return new_pos; } /* Print the options for a SIFT3D struct to stdout. */ void print_opts_SIFT3D(void) { printf("SIFT3D Options: \n" " --%s [value] \n" " The smallest allowed absolute DoG value, as a fraction \n" " of the largest. Must be on the interval (0, 1]. \n" " (default: %.2f) \n" " --%s [value] \n" " The smallest allowed corner score, on the interval \n" " [0, 1]. (default: %.2f) \n" " --%s [value] \n" " The number of pyramid levels per octave in which \n" " keypoints are found. Must be a positive integer. \n" " (default: %d) \n" " --%s [value] \n" " The nominal scale parameter of the input data, on the \n" " interval (0, inf). (default: %.2f) \n" " --%s [value] \n" " The scale parameter of the first level of octave 0, on \n" " the interval (0, inf). (default: %.2f) \n", opt_peak_thresh, peak_thresh_default, opt_corner_thresh, corner_thresh_default, opt_num_kp_levels, num_kp_levels_default, opt_sigma_n, sigma_n_default, opt_sigma0, sigma0_default); } /* Set the parameters of a SIFT3D struct from the given command line * arguments. The argument SIFT3D must be initialized with * init_SIFT3D prior to calling this function. * * On return, all processed SIFT3D options will be removed from argv. * Use argc_ret to get the number of remaining options. * * Options: * --peak_thresh - threshold on DoG extrema magnitude (double) * --corner_thresh - threshold on edge score (double) * --num_kp_levels - number of levels per octave for keypoint * candidates (int) * --sigma_n - base level of blurring assumed in data (double) * --sigma0 - level to blur base of pyramid (double) * * Parameters: * argc - The number of arguments * argv - An array of strings of arguments. All unproccesed arguments are * permuted to the end. * sift3d - The struct to be initialized * check_err - If nonzero, report unrecognized options as errors * * Return value: * Returns the new number of arguments in argv, or -1 on failure. */ int parse_args_SIFT3D(SIFT3D *const sift3d, const int argc, char **argv, const int check_err) { unsigned char *processed; double dval; int c, err, ival, argc_new; #define PEAK_THRESH 'a' #define CORNER_THRESH 'b' #define NUM_KP_LEVELS 'c' #define SIGMA_N 'd' #define SIGMA0 'e' // Options const struct option longopts[] = { {opt_peak_thresh, required_argument, NULL, PEAK_THRESH}, {opt_corner_thresh, required_argument, NULL, CORNER_THRESH}, {opt_num_kp_levels, required_argument, NULL, NUM_KP_LEVELS}, {opt_sigma_n, required_argument, NULL, SIGMA_N}, {opt_sigma0, required_argument, NULL, SIGMA0}, {0, 0, 0, 0} }; // Starting getopt variables const int opterr_start = opterr; // Set the error checking behavior opterr = check_err; // Intialize intermediate data if ((processed = calloc(argc, sizeof(char *))) == NULL) { SIFT3D_ERR("parse_args_SIFT3D: out of memory \n"); return -1; } err = SIFT3D_FALSE; // Process the arguments while ((c = getopt_long(argc, argv, "-", longopts, NULL)) != -1) { const int idx = optind - 1; // Convert the value to double and integer if (optarg != NULL) { dval = atof(optarg); ival = atoi(optarg); } switch (c) { case PEAK_THRESH: if (set_peak_thresh_SIFT3D(sift3d, dval)) goto parse_args_quit; processed[idx - 1] = SIFT3D_TRUE; processed[idx] = SIFT3D_TRUE; break; case CORNER_THRESH: if (set_corner_thresh_SIFT3D(sift3d, dval)) goto parse_args_quit; processed[idx - 1] = SIFT3D_TRUE; processed[idx] = SIFT3D_TRUE; break; case NUM_KP_LEVELS: // Check for errors if (ival <= 0) { SIFT3D_ERR("SIFT3D num_kp_levels " "must be positive. Provided: " "%d \n", ival); goto parse_args_quit; } if (set_num_kp_levels_SIFT3D(sift3d, ival)) goto parse_args_quit; processed[idx - 1] = SIFT3D_TRUE; processed[idx] = SIFT3D_TRUE; break; case SIGMA_N: set_sigma_n_SIFT3D(sift3d, dval); processed[idx - 1] = SIFT3D_TRUE; processed[idx] = SIFT3D_TRUE; break; case SIGMA0: set_sigma0_SIFT3D(sift3d, dval); processed[idx - 1] = SIFT3D_TRUE; processed[idx] = SIFT3D_TRUE; break; case '?': default: if (!check_err) continue; err = SIFT3D_TRUE; } } #undef PEAK_THRESH #undef CORNER_THRESH #undef NUM_KP_LEVELS #undef SIGMA_N #undef SIGMA0 // Put all unprocessed options at the end argc_new = argv_remove(argc, argv, processed); // Return to the default settings opterr = opterr_start; // Clean up free(processed); // Return an error, if error checking is enabled if (check_err && err) return -1; // Reset the state of getopt optind = 0; return argc_new; parse_args_quit: free(processed); return -1; } /* Helper routine to begin processing a new image. If the dimensions differ * from the last one, this function resizes the SIFT3D struct. */ static int set_im_SIFT3D(SIFT3D *const sift3d, const Image *const im) { int dims_old[IM_NDIMS]; int i; const float *const data_old = sift3d->im.data; const Pyramid *const gpyr = &sift3d->gpyr; const int first_octave = sift3d->gpyr.first_octave; const int num_kp_levels = gpyr->num_kp_levels; // Make a temporary copy the previous image dimensions for (i = 0; i < IM_NDIMS; i++) { dims_old[i] = SIFT3D_IM_GET_DIMS(&sift3d->im)[i]; } // Make a copy of the input image if (im_copy_data(im, &sift3d->im)) return SIFT3D_FAILURE; // Scale the input image to [-1, 1] im_scale(&sift3d->im); // Resize the internal data, if necessary if ((data_old == NULL || memcmp(dims_old, SIFT3D_IM_GET_DIMS(&sift3d->im), IM_NDIMS * sizeof(int))) && resize_SIFT3D(sift3d, num_kp_levels)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Helper function to set the scale parameters for a SIFT3D struct. */ static int set_scales_SIFT3D(SIFT3D *const sift3d, const double sigma0, const double sigma_n) { Pyramid *const gpyr = &sift3d->gpyr; Pyramid *const dog = &sift3d->dog; GSS_filters *const gss = &sift3d->gss; // Set the scales for the GSS and DOG pyramids if (set_scales_Pyramid(sigma0, sigma_n, gpyr) || set_scales_Pyramid(sigma0, sigma_n, dog)) return SIFT3D_FAILURE; // Do nothing more if we have no image if (sift3d->im.data == NULL) return SIFT3D_SUCCESS; // Recompute the filters return make_gss(gss, gpyr); } /* Resize a SIFT3D struct, allocating temporary storage and recompiling the * filters. Does nothing unless set_im_SIFT3D was previously called. */ static int resize_SIFT3D(SIFT3D *const sift3d, const int num_kp_levels) { int num_octaves; const Image *const im = &sift3d->im; Pyramid *const gpyr = &sift3d->gpyr; Pyramid *const dog = &sift3d->dog; const unsigned int num_dog_levels = num_kp_levels + 2; const unsigned int num_gpyr_levels = num_dog_levels + 1; const int first_octave = 0; const int first_level = -1; // Compute the meximum allowed number of octaves if (im->data != NULL) { // The minimum size of a pyramid level is 8 in any dimension const int last_octave = (int) log2((double) SIFT3D_MIN(SIFT3D_MIN(im->nx, im->ny), im->nz)) - 3 - first_octave; // Verify octave parameters if (last_octave < first_octave) { SIFT3D_ERR("resize_SIFT3D: input image is too small: " "must have at least 8 voxels in each " "dimension \n"); return SIFT3D_FAILURE; } num_octaves = last_octave - first_octave + 1; } else { num_octaves = 0; } // Resize the pyramid if (resize_Pyramid(im, first_level, num_kp_levels, num_gpyr_levels, first_octave, num_octaves, gpyr) || resize_Pyramid(im, first_level, num_kp_levels, num_dog_levels, first_octave, num_octaves, dog)) return SIFT3D_FAILURE; // Do nothing more if we have no image if (im->data == NULL) return SIFT3D_SUCCESS; // Compute the Gaussian filters if (make_gss(&sift3d->gss, &sift3d->gpyr)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Build the GSS pyramid on a single CPU thread */ static int build_gpyr(SIFT3D *sift3d) { const Image *prev; Sep_FIR_filter *f; Image *cur; int o, s; Pyramid *const gpyr = &sift3d->gpyr; const GSS_filters *const gss = &sift3d->gss; const int s_start = gpyr->first_level + 1; const int s_end = SIFT3D_PYR_LAST_LEVEL(gpyr); const int o_start = gpyr->first_octave; const int o_end = SIFT3D_PYR_LAST_OCTAVE(gpyr); const double unit = 1.0; // Build the first image cur = SIFT3D_PYR_IM_GET(gpyr, o_start, s_start - 1); prev = &sift3d->im; #ifdef SIFT3D_USE_OPENCL if (im_load_cl(cur, SIFT3D_FALSE)) return SIFT3D_FAILURE; #endif f = (Sep_FIR_filter *) &gss->first_gauss.f; if (apply_Sep_FIR_filter(prev, cur, f, unit)) return SIFT3D_FAILURE; // Build the rest of the pyramid SIFT3D_PYR_LOOP_LIMITED_START(o, s, o_start, o_end, s_start, s_end) cur = SIFT3D_PYR_IM_GET(gpyr, o, s); prev = SIFT3D_PYR_IM_GET(gpyr, o, s - 1); f = &gss->gauss_octave[s].f; if (apply_Sep_FIR_filter(prev, cur, f, unit)) return SIFT3D_FAILURE; #ifdef SIFT3D_USE_OPENCL if (im_read_back(cur, SIFT3D_FALSE)) return SIFT3D_FAILURE; #endif SIFT3D_PYR_LOOP_SCALE_END // Downsample if (o != o_end) { const int downsample_level = SIFT3D_MAX(s_end - 2, gpyr->first_level); prev = SIFT3D_PYR_IM_GET(gpyr, o, downsample_level); cur = SIFT3D_PYR_IM_GET(gpyr, o + 1, s_start - 1); assert(fabs(prev->s - cur->s) < FLT_EPSILON); if (im_downsample_2x(prev, cur)) return SIFT3D_FAILURE; } SIFT3D_PYR_LOOP_OCTAVE_END #ifdef SIFT3D_USE_OPENCL clFinish_all(); #endif return SIFT3D_SUCCESS; } static int build_dog(SIFT3D *sift3d) { Image *gpyr_cur, *gpyr_next, *dog_level; int o, s; Pyramid *const dog = &sift3d->dog; Pyramid *const gpyr = &sift3d->gpyr; SIFT3D_PYR_LOOP_START(dog, o, s) gpyr_cur = SIFT3D_PYR_IM_GET(gpyr, o, s); gpyr_next = SIFT3D_PYR_IM_GET(gpyr, o, s + 1); dog_level = SIFT3D_PYR_IM_GET(dog, o, s); if (im_subtract(gpyr_cur, gpyr_next, dog_level)) return SIFT3D_FAILURE; SIFT3D_PYR_LOOP_END return SIFT3D_SUCCESS; } /* Detect local extrema */ static int detect_extrema(SIFT3D *sift3d, Keypoint_store *kp) { Image *cur, *prev, *next; Keypoint *key; float pcur, dogmax, peak_thresh; int o, s, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end, num; const Pyramid *const dog = &sift3d->dog; const int o_start = dog->first_octave; const int o_end = SIFT3D_PYR_LAST_OCTAVE(dog); const int s_start = dog->first_level + 1; const int s_end = SIFT3D_PYR_LAST_LEVEL(dog) - 1; // Verify the inputs if (dog->num_levels < 3) { printf("detect_extrema: Requires at least 3 levels per octave, " "provided only %d \n", dog->num_levels); return SIFT3D_FAILURE; } // Initialize dimensions of keypoint store cur = SIFT3D_PYR_IM_GET(dog, o_start, s_start); kp->nx = cur->nx; kp->ny = cur->ny; kp->nz = cur->nz; #define CMP_CUBE(im, x, y, z, CMP, IGNORESELF, val) ( \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y), (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y), (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) - 1, (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) + 1, (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) - 1, (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) - 1, (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) + 1, (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) + 1, (z) - 1, 0) && \ ((val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z), 0 ) || \ IGNORESELF) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y), (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y), (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) - 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) + 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) - 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) - 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) + 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) + 1, (z), 0 ) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y), (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y), (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) - 1, (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) + 1, (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) - 1, (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) - 1, (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y) + 1, (z) + 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y) + 1, (z) + 1, 0) ) #ifdef CUBOID_EXTREMA #define CMP_PREV(im, x, y, z, CMP, val) \ CMP_CUBE(im, x, y, z, CMP, SIFT3D_FALSE, val) #define CMP_CUR(im, x, y, z, CMP, val) \ CMP_CUBE(im, x, y, z, CMP, SIFT3D_TRUE, val) #define CMP_NEXT(im, x, y, z, CMP, val) \ CMP_CUBE(im, x, y, z, CMP, SIFT3D_FALSE, val) #else #define CMP_PREV(im, x, y, z, CMP, val) ( \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z), 0) \ ) #define CMP_CUR(im, x, y, z, CMP, val) ( \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) + 1, (y), (z), 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x) - 1, (y), (z), 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) + 1, (z), 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y) - 1, (z), 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z) - 1, 0) && \ (val) CMP SIFT3D_IM_GET_VOX( (im), (x), (y), (z) + 1, 0) \ ) #define CMP_NEXT(im, x, y, z, CMP, val) \ CMP_PREV(im, x, y, z, CMP, val) #endif num = 0; SIFT3D_PYR_LOOP_LIMITED_START(o, s, o_start, o_end, s_start, s_end) // Select current and neighboring levels prev = SIFT3D_PYR_IM_GET(dog, o, s - 1); cur = SIFT3D_PYR_IM_GET(dog, o, s); next = SIFT3D_PYR_IM_GET(dog, o, s + 1); // Find maximum DoG value at this level dogmax = 0.0f; SIFT3D_IM_LOOP_START(cur, x, y, z) dogmax = SIFT3D_MAX(dogmax, fabsf(SIFT3D_IM_GET_VOX(cur, x, y, z, 0))); SIFT3D_IM_LOOP_END // Adjust threshold peak_thresh = sift3d->peak_thresh * dogmax; // Loop through all non-boundary pixels x_start = y_start = z_start = 1; x_end = cur->nx - 2; y_end = cur->ny - 2; z_end = cur->nz - 2; SIFT3D_IM_LOOP_LIMITED_START(cur, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end) // Sample the center value pcur = SIFT3D_IM_GET_VOX(cur, x, y, z, 0); // Apply the peak threshold if ((pcur > peak_thresh || pcur < -peak_thresh) && (( // Compare to the neighbors CMP_PREV(prev, x, y, z, >, pcur) && CMP_CUR(cur, x, y, z, >, pcur) && CMP_NEXT(next, x, y, z, >, pcur) ) || ( CMP_PREV(prev, x, y, z, <, pcur) && CMP_CUR(cur, x, y, z, <, pcur) && CMP_NEXT(next, x, y, z, <, pcur)))) { // Add a keypoint candidate num++; if (resize_Keypoint_store(kp, num)) return SIFT3D_FAILURE; key = kp->buf + num - 1; if (init_Keypoint(key)) return SIFT3D_FAILURE; key->o = o; key->s = s; key->sd = cur->s; key->xd = (double) x; key->yd = (double) y; key->zd = (double) z; } SIFT3D_IM_LOOP_END SIFT3D_PYR_LOOP_END #undef CMP_NEIGHBORS return SIFT3D_SUCCESS; } /* Bin a Cartesian gradient into Spherical gradient bins */ SIFT3D_IGNORE_UNUSED static int Cvec_to_sbins(const Cvec * const vd, Svec * const bins) { // Convert to spherical coordinates SIFT3D_CVEC_TO_SVEC(vd, bins); //FIXME: Is this needed? SIFT3D_CVEC_TO_SVEC cannot divide by zero if (bins->mag < FLT_EPSILON * 1E2) return SIFT3D_FAILURE; // Compute bins bins->az *= (float) NBINS_AZ / SIFT3D_AZ_MAX_F; bins->po *= (float) NBINS_PO / SIFT3D_PO_MAX_F; assert(bins->az < NBINS_AZ); assert(bins->po < NBINS_PO); return SIFT3D_SUCCESS; } /* Refine a gradient histogram with optional operations, * such as solid angle weighting. */ static void refine_Hist(Hist *hist) { #ifndef ICOS_HIST #ifdef SIFT3D_ORI_SOLID_ANGLE_WEIGHT { float po; int a, p; // TODO: could accelerate this with a lookup table // Weight by the solid angle of the bins, ignoring constants HIST_LOOP_START(a, p) po = p * po_max_f / NBINS_PO; HIST_GET(hist, a, p) /= cosf(po) - cosf(po + po_max_f / NBINS_PO); HIST_LOOP_END } #endif #endif } /* Assign rotation matrices to the keypoints. * * Note that this stage will modify kp, likely * rejecting some keypoints as orientationally * unstable. */ static int assign_orientations(SIFT3D *const sift3d, Keypoint_store *const kp) { Keypoint *kp_pos; size_t num; int i, err; // Iterate over the keypoints err = SIFT3D_SUCCESS; #pragma omp parallel for for (i = 0; i < kp->slab.num; i++) { Keypoint *const key = kp->buf + i; const Image *const level = SIFT3D_PYR_IM_GET(&sift3d->gpyr, key->o, key->s); Mat_rm *const R = &key->R; const Cvec vcenter = {key->xd, key->yd, key->zd}; const double sigma = ori_sig_fctr * key->sd; // Compute dominant orientations assert(R->u.data_float == key->r_data); switch (assign_orientation_thresh(level, &vcenter, sigma, sift3d->corner_thresh, R)) { case SIFT3D_SUCCESS: // Continue processing this keypoint break; case REJECT: // Mark this keypoint as invalid key->xd = key->yd = key->zd = -1.0; continue; default: // Any other return value is an error err = SIFT3D_FAILURE; continue; } } // Check for errors if (err) return err; // Rebuild the keypoint buffer in place kp_pos = kp->buf; for (i = 0; i < kp->slab.num; i++) { Keypoint *const key = kp->buf + i; // Check if the keypoint is valid if (key->xd < 0.0) continue; // Copy this keypoint to the next available spot if (copy_Keypoint(key, kp_pos)) return SIFT3D_FAILURE; kp_pos++; } // Release unneeded keypoint memory num = kp_pos - kp->buf; return resize_Keypoint_store(kp, num); } /* Helper function to call assign_eig_ori, and reject keypoints with * confidence below the parameter "thresh." All other parameters are the same. * All return values are the same, except REJECT is returned if * conf < thresh. */ static int assign_orientation_thresh(const Image *const im, const Cvec *const vcenter, const double sigma, const double thresh, Mat_rm *const R) { double conf; int ret; ret = assign_eig_ori(im, vcenter, sigma, R, &conf); return ret == SIFT3D_SUCCESS ? (conf < thresh ? REJECT : SIFT3D_SUCCESS) : ret; } /* Assign an orientation to a point in an image. * * Parameters: * -im: The image data. * -vcenter: The center of the window, in image space. * -sigma: The scale parameter. The width of the window is a constant * multiple of this. * -R: The place to write the rotation matrix. */ static int assign_eig_ori(const Image *const im, const Cvec *const vcenter, const double sigma, Mat_rm *const R, double *const conf) { Cvec v[2]; Mat_rm A, L, Q; Cvec vd_win, vdisp, vr; double d, cos_ang, abs_cos_ang, corner_score; float weight, sq_dist, sgn; int i, x, y, z, m; const double win_radius = sigma * ori_rad_fctr; // Verify inputs if (!SIFT3D_IM_CONTAINS_CVEC(im, vcenter)) { SIFT3D_ERR("assign_eig_ori: vcenter (%f, %f, %f) lies " "outside the boundaries of im [%d x %d x %d] \n", vcenter->x, vcenter->y, vcenter->z, im->nx, im->ny, im->nz); return SIFT3D_FAILURE; } if (sigma < 0) { SIFT3D_ERR("assign_eig_ori: invalid sigma: %f \n", sigma); return SIFT3D_FAILURE; } // Initialize the intermediates if (init_Mat_rm(&A, 3, 3, SIFT3D_DOUBLE, SIFT3D_TRUE)) return SIFT3D_FAILURE; if (init_Mat_rm(&L, 0, 0, SIFT3D_DOUBLE, SIFT3D_TRUE) || init_Mat_rm(&Q, 0, 0, SIFT3D_DOUBLE, SIFT3D_TRUE)) goto eig_ori_fail; // Resize the output R->num_rows = R->num_cols = IM_NDIMS; R->type = SIFT3D_FLOAT; if (resize_Mat_rm(R)) goto eig_ori_fail; // Form the structure tensor and window gradient vd_win.x = 0.0f; vd_win.y = 0.0f; vd_win.z = 0.0f; IM_LOOP_SPHERE_START(im, x, y, z, vcenter, win_radius, &vdisp, sq_dist) Cvec vd; // Compute Gaussian weighting, ignoring the constant factor weight = expf(-0.5 * sq_dist / (sigma * sigma)); // Get the gradient IM_GET_GRAD_ISO(im, x, y, z, 0, &vd); // Update the structure tensor SIFT3D_MAT_RM_GET(&A, 0, 0, double) += (double) vd.x * vd.x * weight; SIFT3D_MAT_RM_GET(&A, 0, 1, double) += (double) vd.x * vd.y * weight; SIFT3D_MAT_RM_GET(&A, 0, 2, double) += (double) vd.x * vd.z * weight; SIFT3D_MAT_RM_GET(&A, 1, 1, double) += (double) vd.y * vd.y * weight; SIFT3D_MAT_RM_GET(&A, 1, 2, double) += (double) vd.y * vd.z * weight; SIFT3D_MAT_RM_GET(&A, 2, 2, double) += (double) vd.z * vd.z * weight; // Update the window gradient SIFT3D_CVEC_SCALE(&vd, weight); SIFT3D_CVEC_OP(&vd_win, &vd, +, &vd_win); IM_LOOP_SPHERE_END // Fill in the remaining elements SIFT3D_MAT_RM_GET(&A, 1, 0, double) = SIFT3D_MAT_RM_GET(&A, 0, 1, double); SIFT3D_MAT_RM_GET(&A, 2, 0, double) = SIFT3D_MAT_RM_GET(&A, 0, 2, double); SIFT3D_MAT_RM_GET(&A, 2, 1, double) = SIFT3D_MAT_RM_GET(&A, 1, 2, double); // Reject keypoints with weak gradient if (SIFT3D_CVEC_L2_NORM_SQ(&vd_win) < (float) ori_grad_thresh) { goto eig_ori_reject; } // Get the eigendecomposition if (eigen_Mat_rm(&A, &Q, &L)) goto eig_ori_fail; // Ensure we have distinct eigenvalues m = L.num_rows; if (m != 3) goto eig_ori_reject; // Test the eigenvectors for stability for (i = 0; i < m - 1; i++) { if (fabs(SIFT3D_MAT_RM_GET(&L, i, 0, double) / SIFT3D_MAT_RM_GET(&L, i + 1, 0, double)) > max_eig_ratio) goto eig_ori_reject; } // Assign signs to the first n - 1 vectors corner_score = DBL_MAX; for (i = 0; i < m - 1; i++) { const int eig_idx = m - i - 1; // Get an eigenvector, in descending order vr.x = (float) SIFT3D_MAT_RM_GET(&Q, 0, eig_idx, double); vr.y = (float) SIFT3D_MAT_RM_GET(&Q, 1, eig_idx, double); vr.z = (float) SIFT3D_MAT_RM_GET(&Q, 2, eig_idx, double); // Get the directional derivative d = SIFT3D_CVEC_DOT(&vd_win, &vr); // Get the cosine of the angle between the eigenvector and the gradient cos_ang = d / (SIFT3D_CVEC_L2_NORM(&vr) * SIFT3D_CVEC_L2_NORM(&vd_win)); abs_cos_ang = fabs(cos_ang); // Compute the corner confidence score corner_score = SIFT3D_MIN(corner_score, abs_cos_ang); // Get the sign of the derivative sgn = d > 0.0 ? 1.0f : -1.0f; // Enforce positive directional derivative SIFT3D_CVEC_SCALE(&vr, sgn); // Add the vector to the rotation matrix SIFT3D_MAT_RM_GET(R, 0, i, float) = vr.x; SIFT3D_MAT_RM_GET(R, 1, i, float) = vr.y; SIFT3D_MAT_RM_GET(R, 2, i, float) = vr.z; // Save this vector for later use v[i] = vr; } // Take the cross product of the first two vectors SIFT3D_CVEC_CROSS(v, v + 1, &vr); // Add the last vector SIFT3D_MAT_RM_GET(R, 0, 2, float) = (float) vr.x; SIFT3D_MAT_RM_GET(R, 1, 2, float) = (float) vr.y; SIFT3D_MAT_RM_GET(R, 2, 2, float) = (float) vr.z; // Optionally write back the corner score if (conf != NULL) *conf = corner_score; cleanup_Mat_rm(&A); cleanup_Mat_rm(&Q); cleanup_Mat_rm(&L); return SIFT3D_SUCCESS; eig_ori_reject: if (conf != NULL) *conf = 0.0; cleanup_Mat_rm(&A); cleanup_Mat_rm(&Q); cleanup_Mat_rm(&L); return REJECT; eig_ori_fail: if (conf != NULL) *conf = 0.0; cleanup_Mat_rm(&A); cleanup_Mat_rm(&Q); cleanup_Mat_rm(&L); return SIFT3D_FAILURE; } /* Assign an orientation to a point in an image. * * Parameters: * -im: The image data. * -kp: A container holding the keypoints. On successful return, the matrices * "R" in these keypoints will be set to the assigned orientations. * -conf: If not NULL, this is a pointer to array where the confidence scores * are written. The scores are normally in the interval [0, 1], where 1 * is the most confident, 0 is the least. A higher score means the assigned * orientation is more likely to be robust. A negative score means the * orientation could not be assigned. * * If not NULL, this function will re-allocate conf. As such, *conf must either * be NULL or a pointer to a previously-allocated array. * * Return value: * SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int SIFT3D_assign_orientations(const SIFT3D *const sift3d, const Image *const im, Keypoint_store *const kp, double **const conf) { Image im_smooth; Keypoint key_base; int i; const int num = kp->slab.num; // Verify inputs if (verify_keys(kp, im)) return SIFT3D_FAILURE; // Initialize intermediates init_im(&im_smooth); init_Keypoint(&key_base); // Resize conf (num cannot be zero) if ((*conf = SIFT3D_safe_realloc(*conf, num * sizeof(double))) == NULL) goto assign_orientations_quit; // Smooth and scale the input if (smooth_scale_raw_input(sift3d, im, &im_smooth)) goto assign_orientations_quit; // Assign each orientation for (i = 0; i < num; i++) { Cvec vcenter; int j; Keypoint *const key = kp->buf + i; double *const conf_ret = *conf + i; Mat_rm *const R = &key->R; // Convert the keypoint to the base octave if (keypoint2base(key, &key_base)) goto assign_orientations_quit; // Convert the keypoint to a vector vcenter.x = key_base.xd; vcenter.y = key_base.yd; vcenter.z = key_base.zd; // Assign the orientation switch (assign_eig_ori(&im_smooth, &vcenter, key_base.sd, R, conf_ret)) { case SIFT3D_SUCCESS: break; case REJECT: // Set R to identity if (identity_Mat_rm(IM_NDIMS, R)) goto assign_orientations_quit; *conf_ret = -1.0; break; default: // Stop processing if an error occurs goto assign_orientations_quit; } } // Clean up im_free(&im_smooth); return SIFT3D_SUCCESS; assign_orientations_quit: im_free(&im_smooth); return SIFT3D_FAILURE; } /* Detect keypoint locations and orientations. You must initialize * the SIFT3D struct, image, and keypoint store with the appropriate * functions prior to calling this function. */ int SIFT3D_detect_keypoints(SIFT3D *const sift3d, const Image *const im, Keypoint_store *const kp) { // Verify inputs if (im->nc != 1) { SIFT3D_ERR("SIFT3D_detect_keypoints: invalid number " "of image channels: %d -- only single-channel images " "are supported \n", im->nc); return SIFT3D_FAILURE; } // Set the image if (set_im_SIFT3D(sift3d, im)) return SIFT3D_FAILURE; // Build the GSS pyramid if (build_gpyr(sift3d)) return SIFT3D_FAILURE; // Build the DoG pyramid if (build_dog(sift3d)) return SIFT3D_FAILURE; // Detect extrema if (detect_extrema(sift3d, kp)) return SIFT3D_FAILURE; // Assign orientations if (assign_orientations(sift3d, kp)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Get the bin and barycentric coordinates of a vector in the icosahedral * histogram. */ SIFT3D_IGNORE_UNUSED static int icos_hist_bin(const SIFT3D * const sift3d, const Cvec * const x, Cvec * const bary, int * const bin) { float k; int i; const Mesh * const mesh = &sift3d->mesh; // Check for very small vectors if (SIFT3D_CVEC_L2_NORM_SQ(x) < bary_eps) return SIFT3D_FAILURE; // Iterate through the faces for (i = 0; i < ICOS_NFACES; i++) { const Tri * const tri = mesh->tri + i; // Convert to barycentric coordinates if (cart2bary(x, tri, bary, &k)) continue; // Test for intersection if (bary->x < -bary_eps || bary->y < -bary_eps || bary->z < -bary_eps || k < 0) continue; // Save the bin *bin = i; // No other triangles will be intersected return SIFT3D_SUCCESS; } // Unreachable code assert(SIFT3D_FALSE); return SIFT3D_FAILURE; } /* Helper routine to interpolate over the histograms of a * SIFT3D descriptor. */ void SIFT3D_desc_acc_interp(const SIFT3D * const sift3d, const Cvec * const vbins, const Cvec * const grad, SIFT3D_Descriptor * const desc) { Cvec dvbins; Hist *hist; float weight; int dx, dy, dz, x, y, z; #ifdef ICOS_HIST Cvec bary; float mag; int bin; #else Svec sbins, dsbins; int da, dp, a, p; #endif const int y_stride = NHIST_PER_DIM; const int z_stride = NHIST_PER_DIM * NHIST_PER_DIM; // Compute difference from integer bin values dvbins.x = vbins->x - floorf(vbins->x); dvbins.y = vbins->y - floorf(vbins->y); dvbins.z = vbins->z - floorf(vbins->z); // Compute the histogram bin #ifdef ICOS_HIST const Mesh *const mesh = &sift3d->mesh; // Get the index of the intersecting face if (icos_hist_bin(sift3d, grad, &bary, &bin)) return; // Get the magnitude of the vector mag = SIFT3D_CVEC_L2_NORM(grad); #else if (Cvec_to_sbins(grad, &sbins)) return; dsbins.az = sbins.az - floorf(sbins.az); dsbins.po = sbins.po - floorf(sbins.po); #endif for (dx = 0; dx < 2; dx++) { for (dy = 0; dy < 2; dy++) { for (dz = 0; dz < 2; dz++) { x = (int) vbins->x + dx; y = (int) vbins->y + dy; z = (int) vbins->z + dz; // Check boundaries if (x < 0 || x >= NHIST_PER_DIM || y < 0 || y >= NHIST_PER_DIM || z < 0 || z >= NHIST_PER_DIM) continue; // Get the histogram hist = desc->hists + x + y * y_stride + z * z_stride; assert(x + y * y_stride + z * z_stride < DESC_NUM_TOTAL_HIST); // Get the spatial interpolation weight weight = ((dx == 0) ? (1.0f - dvbins.x) : dvbins.x) * ((dy == 0) ? (1.0f - dvbins.y) : dvbins.y) * ((dz == 0) ? (1.0f - dvbins.z) : dvbins.z); /* Add the value into the histogram */ #ifdef ICOS_HIST assert(HIST_NUMEL == ICOS_NVERT); assert(bin >= 0 && bin < ICOS_NFACES); // Interpolate over three vertices MESH_HIST_GET(mesh, hist, bin, 0) += mag * weight * bary.x; MESH_HIST_GET(mesh, hist, bin, 1) += mag * weight * bary.y; MESH_HIST_GET(mesh, hist, bin, 2) += mag * weight * bary.z; #else // Iterate over all angles for (dp = 0; dp < 2; dp ++) { for (da = 0; da < 2; da ++) { a = ((int) sbins.az + da) % NBINS_AZ; p = (int) sbins.po + dp; if (p >= NBINS_PO) { // See HIST_GET_PO a = (a + NBINS_AZ / 2) % NBINS_AZ; p = NBINS_PO - 1; } assert(a >= 0); assert(a < NBINS_AZ); assert(p >= 0); assert(p < NBINS_PO); HIST_GET(hist, a, p) += sbins.mag * weight * ((da == 0) ? (1.0f - dsbins.az) : dsbins.az) * ((dp == 0) ? (1.0f - dsbins.po) : dsbins.po); }} #endif }}} } /* Normalize a descriptor */ static void normalize_desc(SIFT3D_Descriptor * const desc) { double norm; int i, a, p; norm = 0.0; for (i = 0; i < DESC_NUM_TOTAL_HIST; i++) { const Hist *const hist = desc->hists + i; HIST_LOOP_START(a, p) const float el = HIST_GET(hist, a, p); norm += (double) el * el; HIST_LOOP_END } norm = sqrt(norm) + DBL_EPSILON; for (i = 0; i < DESC_NUM_TOTAL_HIST; i++) { Hist *const hist = desc->hists + i; const float norm_inv = 1.0f / norm; HIST_LOOP_START(a, p) HIST_GET(hist, a, p) *= norm_inv; HIST_LOOP_END } } /* Set a histogram to zero */ static void hist_zero(Hist *hist) { int a, p; HIST_LOOP_START(a, p) HIST_GET(hist, a, p) = 0.0f; HIST_LOOP_END } /* Helper routine to extract a single SIFT3D descriptor */ static int extract_descrip(SIFT3D *const sift3d, const Image *const im, const Keypoint *const key, SIFT3D_Descriptor *const desc) { float buf[IM_NDIMS * IM_NDIMS]; Mat_rm Rt; Cvec vcenter, vim, vkp, vbins, grad, grad_rot; Hist *hist; float weight, sq_dist; int i, x, y, z, a, p; // Compute basic parameters const float sigma = key->sd * desc_sig_fctr; const float win_radius = desc_rad_fctr * sigma; const float desc_half_width = win_radius / sqrt(2); const float desc_width = 2.0f * desc_half_width; const float desc_hist_width = desc_width / NHIST_PER_DIM; const float desc_bin_fctr = 1.0f / desc_hist_width; const double coord_factor = ldexp(1.0, key->o); // Invert the rotation matrix if (init_Mat_rm_p(&Rt, buf, IM_NDIMS, IM_NDIMS, SIFT3D_FLOAT, SIFT3D_FALSE) || transpose_Mat_rm(&key->R, &Rt)) return SIFT3D_FAILURE; // Zero the descriptor for (i = 0; i < DESC_NUM_TOTAL_HIST; i++) { hist = desc->hists + i; hist_zero(hist); } // Iterate over a sphere window in real-world coordinates vcenter.x = key->xd; vcenter.y = key->yd; vcenter.z = key->zd; IM_LOOP_SPHERE_START(im, x, y, z, &vcenter, win_radius, &vim, sq_dist) // Rotate to keypoint space SIFT3D_MUL_MAT_RM_CVEC(&Rt, &vim, &vkp); // Compute spatial bins vbins.x = (vkp.x + desc_half_width) * desc_bin_fctr; vbins.y = (vkp.y + desc_half_width) * desc_bin_fctr; vbins.z = (vkp.z + desc_half_width) * desc_bin_fctr; // Reject points outside the rectangular descriptor if (vbins.x < 0 || vbins.y < 0 || vbins.z < 0 || vbins.x >= (float) NHIST_PER_DIM || vbins.y >= (float) NHIST_PER_DIM || vbins.z >= (float) NHIST_PER_DIM) continue; // Take the gradient IM_GET_GRAD_ISO(im, x, y, z, 0, &grad); // Apply a Gaussian window weight = expf(-0.5f * sq_dist / (sigma * sigma)); SIFT3D_CVEC_SCALE(&grad, weight); // Rotate the gradient to keypoint space SIFT3D_MUL_MAT_RM_CVEC(&Rt, &grad, &grad_rot); // Finally, accumulate bins by 5x linear interpolation SIFT3D_desc_acc_interp(sift3d, &vbins, &grad_rot, desc); IM_LOOP_SPHERE_END // Histogram refinement steps for (i = 0; i < DESC_NUM_TOTAL_HIST; i++) { refine_Hist(&desc->hists[i]); } // Normalize the descriptor normalize_desc(desc); // Truncate for (i = 0; i < DESC_NUM_TOTAL_HIST; i++) { hist = desc->hists + i; HIST_LOOP_START(a, p) HIST_GET(hist, a, p) = SIFT3D_MIN(HIST_GET(hist, a, p), (float) trunc_thresh); HIST_LOOP_END } // Normalize again normalize_desc(desc); // Save the descriptor location in the original image // coordinates desc->xd = key->xd * coord_factor; desc->yd = key->yd * coord_factor; desc->zd = key->zd * coord_factor; desc->sd = key->sd; return SIFT3D_SUCCESS; } /* Check if the Gaussian scale-space pyramid in a SIFT3D struct is valid. This * shall return SIFT3D_TRUE if the struct was initialized, and * SIFT3D_detect_keypoints has been successfully called on it since * initialization. * * Note: sift3d must be initialized before calling this function. */ int SIFT3D_have_gpyr(const SIFT3D *const sift3d) { const Pyramid *const gpyr = &sift3d->gpyr; return gpyr->levels != NULL && gpyr->num_levels != 0 && gpyr->num_octaves != 0; } /* Helper function to scale keypoint coordinates. * * Parameters: * -src: The source keypoints. * -factors: An array of length IM_NDIMS specifying the scaling factors. * -dst: The scaled keypoints. * * Return: SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int scale_Keypoint(const Keypoint *const src, const double *const factors, Keypoint *const dst) { // Copy the source keypoint if (copy_Keypoint(src, dst)) { SIFT3D_ERR("scale_Keypoint: failed to convert keypoints \n"); return SIFT3D_FAILURE; } // Scale the coordinates dst->xd *= factors[0]; dst->yd *= factors[1]; dst->zd *= factors[2]; return SIFT3D_SUCCESS; } /* Helper function to smooth and scale a "raw" input image, as if it were * processed via SIFT3D_detect_keypoints. * * Parameters: * -sift3d: Stores the parameters sigma_n and sigma0. * -src: The input "raw" image. * -dst: The output, smoothed image. * * Return: SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ static int smooth_scale_raw_input(const SIFT3D *const sift3d, const Image *const src, Image *const dst) { Gauss_filter gauss; const double sigma_n = sift3d->gpyr.sigma_n; const double sigma0 = sift3d->gpyr.sigma0; const double unit = 1.0; // Initialize the smoothing filter if (init_Gauss_incremental_filter(&gauss, sigma_n, sigma0, IM_NDIMS)) return SIFT3D_FAILURE; // Smooth the input if (apply_Sep_FIR_filter(src, dst, &gauss.f, unit)) goto smooth_scale_raw_input_quit; // Scale the input to [-1, 1] im_scale(dst); // Clean up cleanup_Gauss_filter(&gauss); return SIFT3D_SUCCESS; smooth_scale_raw_input_quit: cleanup_Gauss_filter(&gauss); return SIFT3D_FAILURE; } /* Extract SIFT3D descriptors from a list of keypoints. Uses the Gaussian * scale-space pyramid from the previous call to SIFT3D_detect_keypoints on * this SIFT3D struct. To extract from an image, see * SIFT3D_extract_raw_descriptors. * * Note: To check if SIFT3D_detect_keypoints has been called on this struct, * use SIFT3D_have_gpyr. * * Parameters: * sift3d - (initialized) struct defining the algorithm parameters. Must have * been used in some previous call to SIFT3D_detect_keypoints. * kp - keypoint list populated by a feature detector * desc - (initialized) struct to hold the descriptors * * Return value: * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int SIFT3D_extract_descriptors(SIFT3D *const sift3d, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc) { // Verify inputs if (verify_keys(kp, &sift3d->im)) return SIFT3D_FAILURE; // Check if a Gaussian scale-space pyramid is available for processing if (!SIFT3D_have_gpyr(sift3d)) { SIFT3D_ERR("SIFT3D_extract_descriptors: no Gaussian pyramid is " "available. Make sure SIFT3D_detect_keypoints was " "called prior to calling this function. \n"); return SIFT3D_FAILURE; } // Extract features if (_SIFT3D_extract_descriptors(sift3d, &sift3d->gpyr, kp, desc)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Verify that keypoints kp are valid in image im. Returns SIFT3D_SUCCESS if * valid, SIFT3D_FAILURE otherwise. */ static int verify_keys(const Keypoint_store *const kp, const Image *const im) { int i; const int num = kp->slab.num; // Check the number of keypoints if (num < 1) { SIFT3D_ERR("verify_keys: invalid number of keypoints: " "%d \n", num); return SIFT3D_FAILURE; } // Check each keypoint for (i = 0; i < num; i++) { const Keypoint *key = kp->buf + i; const double octave_factor = ldexp(1.0, key->o); if (key->xd < 0 || key->yd < 0 || key->zd < 0 || key->xd * octave_factor >= (double) im->nx || key->yd * octave_factor >= (double) im->ny || key->zd * octave_factor >= (double) im->nz) { SIFT3D_ERR("verify_keys: keypoint %d (%f, %f, %f) " "octave %d exceeds image dimensions " "(%d, %d, %d) \n", i, key->xd, key->yd, key->zd, key->o, im->nx, im->ny, im->nz); return SIFT3D_FAILURE; } if (key->sd <= 0) { SIFT3D_ERR("verify_keys: keypoint %d has invalid " "scale %f \n", i, key->sd); return SIFT3D_FAILURE; } } return SIFT3D_SUCCESS; } /* Convert the keypoint src to the equivalent one at octave 0, stored in dst. */ static int keypoint2base(const Keypoint *const src, Keypoint *const dst) { double base_factors[IM_NDIMS]; int j; const double octave_factor = ldexp(1.0, src->o); // Convert the factors to the base octave for (j = 0; j < IM_NDIMS; j++) { base_factors[j] = octave_factor; } // Scale the keypoint if (scale_Keypoint(src, base_factors, dst)) return SIFT3D_FAILURE; // Assign the keypoint the base octave and scale level dst->o = 0; dst->s = 0; return SIFT3D_SUCCESS; } /* Extract SIFT3D descriptors from a list of keypoints and * an image. To extract from a Gaussian scale-space pyramid, see * SIFT3D_extract_descriptors. * * Parameters: * sift3d - (initialized) struct defining the algorithm parameters * im - Pointer to an Image struct. A copy of the image is smoothed from * sift3d->sigma_n to sift3d->sigma0 prior to descriptor extraction. * kp - keypoint list populated by a feature detector * desc - (initialized) struct to hold the descriptors * * Return value: * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int SIFT3D_extract_raw_descriptors(SIFT3D *const sift3d, const Image *const im, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc) { Keypoint_store kp_base; Pyramid pyr; Image *level; int i; const int first_octave = 0; const int first_level = 0; const int num_kp_levels = 1; const int num_levels = 1; const int num_octaves = 1; const double sigma0 = sift3d->gpyr.sigma0; const double sigma_n = sift3d->gpyr.sigma_n; // Verify inputs if (verify_keys(kp, im)) return SIFT3D_FAILURE; // Initialize intermediates init_Pyramid(&pyr); init_Keypoint_store(&kp_base); // Initialize the image pyramid set_scales_Pyramid(sigma0, sigma_n, &pyr); if (resize_Pyramid(im, first_level, num_kp_levels, num_levels, first_octave, num_octaves, &pyr)) goto extract_raw_descriptors_quit; // Smooth the input image, storing the result in the pyramid level = SIFT3D_PYR_IM_GET(&pyr, first_octave, first_level); if (smooth_scale_raw_input(sift3d, im, level)) goto extract_raw_descriptors_quit; // Allocate a temporary copy of the keypoints if (resize_Keypoint_store(&kp_base, kp->slab.num)) goto extract_raw_descriptors_quit; // Convert keypoints to the base scale and octave for (i = 0; i < kp->slab.num; i++) { const Keypoint *const src = kp->buf + i; Keypoint *const dst = kp_base.buf + i; if (keypoint2base(src, dst)) goto extract_raw_descriptors_quit; } // Extract the descriptors if (_SIFT3D_extract_descriptors(sift3d, &pyr, &kp_base, desc)) goto extract_raw_descriptors_quit; // Clean up cleanup_Pyramid(&pyr); cleanup_Keypoint_store(&kp_base); return SIFT3D_SUCCESS; extract_raw_descriptors_quit: cleanup_Pyramid(&pyr); cleanup_Keypoint_store(&kp_base); return SIFT3D_FAILURE; } /* Helper funciton to extract SIFT3D descriptors from a list of keypoints and * an image. Called by SIFT3D_extract_descriptors and * SIFT3D_extract_raw_descriptors. * * parameters: * sift3d - (initialized) struct defining the algorithm parameters * gpyr - A Gaussian Scale-Space pyramid containing the image data * kp - keypoint list populated by a feature detector * desc - (initialized) struct to hold the descriptors * use_gpyr - see im for details */ static int _SIFT3D_extract_descriptors(SIFT3D *const sift3d, const Pyramid *const gpyr, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc) { int i, ret; const Image *const first_level = SIFT3D_PYR_IM_GET(gpyr, gpyr->first_octave, gpyr->first_level); const int num = kp->slab.num; // Initialize the metadata desc->nx = first_level->nx; desc->ny = first_level->ny; desc->nz = first_level->nz; // Resize the descriptor store if (resize_SIFT3D_Descriptor_store(desc, num)) return SIFT3D_FAILURE; // Extract the descriptors ret = SIFT3D_SUCCESS; #pragma omp parallel for for (i = 0; i < desc->num; i++) { const Keypoint *const key = kp->buf + i; SIFT3D_Descriptor *const descrip = desc->buf + i; const Image *const level = SIFT3D_PYR_IM_GET(gpyr, key->o, key->s); if (extract_descrip(sift3d, level, key, descrip)) { ret = SIFT3D_FAILURE; } } return ret; } /* L2-normalize a histogram */ static void normalize_hist(Hist *const hist) { double norm; float norm_inv; int a, p; norm = 0.0; HIST_LOOP_START(a, p) const float el = HIST_GET(hist, a, p); norm += (double) el * el; HIST_LOOP_END norm = sqrt(norm) + DBL_EPSILON; norm_inv = 1.0f / norm; HIST_LOOP_START(a, p) HIST_GET(hist, a, p) *= norm_inv; HIST_LOOP_END } /* Dense gradient histogram postprocessing steps */ static void postproc_Hist(Hist *const hist, const float norm) { int a, p; const float hist_trunc = trunc_thresh * DESC_NUMEL / HIST_NUMEL; // Histogram refinement steps refine_Hist(hist); // Normalize the descriptor normalize_hist(hist); // Truncate HIST_LOOP_START(a, p) HIST_GET(hist, a, p) = SIFT3D_MIN(HIST_GET(hist, a, p), hist_trunc); HIST_LOOP_END // Normalize again normalize_hist(hist); // Convert to the desired norm HIST_LOOP_START(a, p) HIST_GET(hist, a, p) *= norm; HIST_LOOP_END } /* Helper routine to extract a single SIFT3D histogram, with rotation. */ static int extract_dense_descrip_rotate(SIFT3D *const sift3d, const Image *const im, const Cvec *const vcenter, const double sigma, const Mat_rm *const R, Hist *const hist) { float buf[IM_NDIMS * IM_NDIMS]; Mat_rm Rt; Cvec grad, grad_rot, bary, vim; float sq_dist, mag, weight; SIFT3D_IGNORE_UNUSED int a, p, x, y, z, bin; const Mesh *const mesh = &sift3d->mesh; const float win_radius = desc_rad_fctr * sigma; // Invert the rotation matrix if (init_Mat_rm_p(&Rt, buf, IM_NDIMS, IM_NDIMS, SIFT3D_FLOAT, SIFT3D_FALSE) || transpose_Mat_rm(R, &Rt)) return SIFT3D_FAILURE; // Zero the descriptor hist_zero(hist); // Iterate over a sphere window in real-world coordinates IM_LOOP_SPHERE_START(im, x, y, z, vcenter, win_radius, &vim, sq_dist) // Take the gradient and rotate IM_GET_GRAD_ISO(im, x, y, z, 0, &grad); SIFT3D_MUL_MAT_RM_CVEC(&Rt, &grad, &grad_rot); // Get the index of the intersecting face if (icos_hist_bin(sift3d, &grad_rot, &bary, &bin)) continue; // Get the magnitude of the vector mag = SIFT3D_CVEC_L2_NORM(&grad); // Get the Gaussian window weight weight = expf(-0.5f * sq_dist / (sigma * sigma)); // Interpolate over three vertices MESH_HIST_GET(mesh, hist, bin, 0) += mag * weight * bary.x; MESH_HIST_GET(mesh, hist, bin, 1) += mag * weight * bary.y; MESH_HIST_GET(mesh, hist, bin, 2) += mag * weight * bary.z; IM_LOOP_SPHERE_END return SIFT3D_SUCCESS; } /* Get a descriptor with a single histogram at each voxel of an image. * The result is an image with HIST_NUMEL channels, where each channel is a * bin of the histogram. * * Parameters: * -sift3d Stores the algorithm parameters. * -in The input image. * -desc The output image of descriptors. */ int SIFT3D_extract_dense_descriptors(SIFT3D *const sift3d, const Image *const in, Image *const desc) { int (*extract_fun)(SIFT3D *const, const Image *const, Image *const); Image in_smooth; int x, y, z; // Verify inputs if (in->nc != 1) { SIFT3D_ERR("SIFT3D_extract_dense_descriptors: invalid " "number of channels: %d. This function only supports " "single-channel images. \n", in->nc); return SIFT3D_FAILURE; } // Select the appropriate subroutine extract_fun = sift3d->dense_rotate ? extract_dense_descriptors_rotate : extract_dense_descriptors_no_rotate; // Resize the output image memcpy(SIFT3D_IM_GET_DIMS(desc), SIFT3D_IM_GET_DIMS(in), IM_NDIMS * sizeof(int)); desc->nc = HIST_NUMEL; im_default_stride(desc); if (im_resize(desc)) return SIFT3D_FAILURE; // Intialize intermediates init_im(&in_smooth); //TODO: Interpolate to be isotropic // Smooth and scale the input image if (smooth_scale_raw_input(sift3d, in, &in_smooth)) goto extract_dense_quit; // Extract the descriptors if (extract_fun(sift3d, &in_smooth, desc)) return SIFT3D_FAILURE; // Post-process the descriptors SIFT3D_IM_LOOP_START(desc, x, y, z) Hist hist; // Get the image intensity at this voxel const float val = SIFT3D_IM_GET_VOX(in, x, y, z, 0); // Copy to a Hist vox2hist(desc, x, y, z, &hist); // Post-process postproc_Hist(&hist, val); // Copy back to the image hist2vox(&hist, desc, x, y, z); SIFT3D_IM_LOOP_END // TODO transform back to original space // Clean up im_free(&in_smooth); return SIFT3D_SUCCESS; extract_dense_quit: im_free(&in_smooth); return SIFT3D_FAILURE; } /* Helper function for SIFT3D_extract_dense_descriptors, without rotation * invariance. This function is much faster than its rotation-invariant * counterpart because histogram bins are pre-computed. */ static int extract_dense_descriptors_no_rotate(SIFT3D *const sift3d, const Image *const in, Image *const desc) { Image temp; Gauss_filter gauss; Cvec grad, bary; int x, y, z, bin; const int x_start = 1; const int y_start = 1; const int z_start = 1; const int x_end = in->nx - 2; const int y_end = in->ny - 2; const int z_end = in->nz - 2; Mesh * const mesh = &sift3d->mesh; const double sigma_win = sift3d->gpyr.sigma0 * desc_sig_fctr / NHIST_PER_DIM; const double unit = 1.0; // Initialize the intermediate image init_im(&temp); if (im_copy_dims(desc, &temp)) return SIFT3D_FAILURE; // Initialize the filter if (init_Gauss_filter(&gauss, sigma_win, 3)) { im_free(&temp); return SIFT3D_FAILURE; } // Initialize the descriptors for each voxel im_zero(&temp); SIFT3D_IM_LOOP_LIMITED_START(in, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end) // Take the gradient IM_GET_GRAD_ISO(in, x, y, z, 0, &grad); // Get the index of the intersecting face if (icos_hist_bin(sift3d, &grad, &bary, &bin)) continue; // Initialize each vertex SIFT3D_IM_GET_VOX(&temp, x, y, z, MESH_GET_IDX(mesh, bin, 0)) = bary.x; SIFT3D_IM_GET_VOX(&temp, x, y, z, MESH_GET_IDX(mesh, bin, 1)) = bary.y; SIFT3D_IM_GET_VOX(&temp, x, y, z, MESH_GET_IDX(mesh, bin, 2)) = bary.z; SIFT3D_IM_LOOP_END // Filter the descriptors if (apply_Sep_FIR_filter(&temp, desc, &gauss.f, unit)) goto dense_extract_quit; // Clean up im_free(&temp); cleanup_Gauss_filter(&gauss); return SIFT3D_SUCCESS; dense_extract_quit: im_free(&temp); cleanup_Gauss_filter(&gauss); return SIFT3D_FAILURE; } /* Copy a voxel to a Hist. Does no bounds checking. */ static void vox2hist(const Image *const im, const int x, const int y, const int z, Hist *const hist) { int c; for (c = 0; c < HIST_NUMEL; c++) { hist->bins[c] = SIFT3D_IM_GET_VOX(im, x, y, z, c); } } /* Copy a Hist to a voxel. Does no bounds checking. */ static void hist2vox(Hist *const hist, const Image *const im, const int x, const int y, const int z) { int c; for (c = 0; c < HIST_NUMEL; c++) { SIFT3D_IM_GET_VOX(im, x, y, z, c) = hist->bins[c]; } } /* As in extract_dense_descrip, but with rotation invariance */ static int extract_dense_descriptors_rotate(SIFT3D *const sift3d, const Image *const in, Image *const desc) { Hist hist; Mat_rm R, Id; Mat_rm *ori; int i, x, y, z; // Initialize the identity matrix if (init_Mat_rm(&Id, 3, 3, SIFT3D_FLOAT, SIFT3D_TRUE)) { return SIFT3D_FAILURE; } for (i = 0; i < 3; i++) { SIFT3D_MAT_RM_GET(&Id, i, i, float) = 1.0f; } // Initialize the rotation matrix if (init_Mat_rm(&R, 3, 3, SIFT3D_FLOAT, SIFT3D_TRUE)) { cleanup_Mat_rm(&Id); return SIFT3D_FAILURE; } // Iterate over each voxel SIFT3D_IM_LOOP_START(in, x, y, z) const Cvec vcenter = {x, y, z}; const double ori_sigma = sift3d->gpyr.sigma0 * ori_sig_fctr; const double desc_sigma = sift3d->gpyr.sigma0 * desc_sig_fctr / NHIST_PER_DIM; // Attempt to assign an orientation switch (assign_orientation_thresh(in, &vcenter, ori_sigma, sift3d->corner_thresh, &R)) { case SIFT3D_SUCCESS: // Use the assigned orientation ori = &R; break; case REJECT: // Default to identity ori = &Id; break; default: // Unexpected error goto dense_rotate_quit; } // Extract the descriptor if (extract_dense_descrip_rotate(sift3d, in, &vcenter, desc_sigma, ori, &hist)) goto dense_rotate_quit; // Copy the descriptor to the image channels hist2vox(&hist, desc, x, y, z); SIFT3D_IM_LOOP_END // Clean up cleanup_Mat_rm(&R); cleanup_Mat_rm(&Id); return SIFT3D_SUCCESS; dense_rotate_quit: // Clean up and return an error condition cleanup_Mat_rm(&R); cleanup_Mat_rm(&Id); return SIFT3D_FAILURE; } /* Convert a keypoint store to a matrix. * Output format: * [x1 y1 z1] * | ... | * [xn yn zn] * * mat must be initialized. */ int Keypoint_store_to_Mat_rm(const Keypoint_store *const kp, Mat_rm *const mat) { int i; const int num = kp->slab.num; // Resize mat mat->num_rows = num; mat->num_cols = IM_NDIMS; mat->type = SIFT3D_DOUBLE; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; // Build the matrix for (i = 0; i < num; i++) { const Keypoint *const key = kp->buf + i; // Adjust the coordinates to the base octave const double coord_factor = ldexp(1.0, key->o); SIFT3D_MAT_RM_GET(mat, i, 0, double) = coord_factor * key->xd; SIFT3D_MAT_RM_GET(mat, i, 1, double) = coord_factor * key->yd; SIFT3D_MAT_RM_GET(mat, i, 2, double) = coord_factor * key->zd; } return SIFT3D_SUCCESS; } /* Convert SIFT3D desriptors to a coordinate matrix. This function has the same * output format as Keypoint_store_to_Mat_rm */ int SIFT3D_Descriptor_coords_to_Mat_rm( const SIFT3D_Descriptor_store *const store, Mat_rm *const mat) { int i; const int num_rows = store->num; const int num_cols = IM_NDIMS; // Verify inputs if (num_rows < 1) { printf("SIFT3D_Descriptor_coords_to_Mat_rm: invalid number of " "descriptors: %d \n", num_rows); return SIFT3D_FAILURE; } // Resize the output mat->type = SIFT3D_DOUBLE; mat->num_rows = num_rows; mat->num_cols = num_cols; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; // Copy the data for (i = 0; i < num_rows; i++) { const SIFT3D_Descriptor *const desc = store->buf + i; SIFT3D_MAT_RM_GET(mat, i, 0, double) = desc->xd; SIFT3D_MAT_RM_GET(mat, i, 1, double) = desc->yd; SIFT3D_MAT_RM_GET(mat, i, 2, double) = desc->zd; } return SIFT3D_SUCCESS; } /* Convert SIFT3D descriptors to a matrix. * * Output format: * [x y z el0 el1 ... el767] * Each row is a feature descriptor. [x y z] are the coordinates in image * space, and [el0 el1 ... el767 are the 768 dimensions of the descriptor. * * mat must be initialized prior to calling this function. mat will be resized. * The type of mat will be changed to float. */ int SIFT3D_Descriptor_store_to_Mat_rm(const SIFT3D_Descriptor_store *const store, Mat_rm *const mat) { int i, j, a, p; const int num_rows = store->num; const int num_cols = IM_NDIMS + DESC_NUMEL; // Verify inputs if (num_rows < 1) { printf("SIFT3D_Descriptor_store_to_Mat_rm: invalid number of " "descriptors: %d \n", num_rows); return SIFT3D_FAILURE; } // Resize inputs mat->type = SIFT3D_FLOAT; mat->num_rows = num_rows; mat->num_cols = num_cols; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; // Copy the data for (i = 0; i < num_rows; i++) { const SIFT3D_Descriptor *const desc = store->buf + i; // Copy the coordinates SIFT3D_MAT_RM_GET(mat, i, 0, float) = (float) desc->xd; SIFT3D_MAT_RM_GET(mat, i, 1, float) = (float) desc->yd; SIFT3D_MAT_RM_GET(mat, i, 2, float) = (float) desc->zd; // Copy the feature vector for (j = 0; j < DESC_NUM_TOTAL_HIST; j++) { const Hist *const hist = desc->hists + j; HIST_LOOP_START(a, p) const int col = DESC_MAT_GET_COL(j, a, p); SIFT3D_MAT_RM_GET(mat, i, col, float) = HIST_GET(hist, a, p); HIST_LOOP_END } } return SIFT3D_SUCCESS; } /* Convert a Mat_rm to a descriptor store. See * SIFT3D_Descriptor_store_to_Mat_rm for the input format. */ int Mat_rm_to_SIFT3D_Descriptor_store(const Mat_rm *const mat, SIFT3D_Descriptor_store *const store) { int i, j, a, p; const int num_rows = mat->num_rows; const int num_cols = mat->num_cols; // Verify inputs if (num_rows < 1 || num_cols != IM_NDIMS + DESC_NUMEL) { SIFT3D_ERR("Mat_rm_to_SIFT3D_Descriptor_store: invalid matrix " "dimensions: [%d X %d] \n", num_rows, num_cols); return SIFT3D_FAILURE; } if (mat->type != SIFT3D_FLOAT) { SIFT3D_ERR("Mat_rm_to_SIFT3D_Descriptor_store: matrix must " "have type SIFT3D_FLOAT"); return SIFT3D_FAILURE; } /* Resize the descriptor store */ if (resize_SIFT3D_Descriptor_store(store, num_rows)) return SIFT3D_FAILURE; // Copy the data for (i = 0; i < num_rows; i++) { SIFT3D_Descriptor *const desc = store->buf + i; // Copy the coordinates desc->xd = SIFT3D_MAT_RM_GET(mat, i, 0, float); desc->yd = SIFT3D_MAT_RM_GET(mat, i, 1, float); desc->zd = SIFT3D_MAT_RM_GET(mat, i, 2, float); desc->sd = sigma0_default; // Copy the feature vector for (j = 0; j < DESC_NUM_TOTAL_HIST; j++) { Hist *const hist = desc->hists + j; HIST_LOOP_START(a, p) const int col = DESC_MAT_GET_COL(j, a, p); HIST_GET(hist, a, p) = SIFT3D_MAT_RM_GET(mat, i, col, float); HIST_LOOP_END } } return SIFT3D_SUCCESS; } /* Convert a list of matches to matrices of point coordinates. * Only valid matches will be included in the output matrices. * * The format of "matches" is specified in SIFT3D_nn_match. * * All matrices must be initialized prior to calling this function. * * Output format: * m x 3 matrices [x11 y11 z11] [x21 y21 z21] * |x12 y12 z12| |x22 y22 z22| * ... ... * [x1N y1N z1N] [x2N y2N z2N] * * Where points on corresponding rows are matches. */ int SIFT3D_matches_to_Mat_rm(SIFT3D_Descriptor_store *d1, SIFT3D_Descriptor_store *d2, const int *const matches, Mat_rm *const match1, Mat_rm *const match2) { int i, num_matches; const int num = d1->num; // Resize matrices match1->num_rows = match2->num_rows = d1->num; match1->num_cols = match2->num_cols = 3; match1->type = match2->type = SIFT3D_DOUBLE; if (resize_Mat_rm(match1) || resize_Mat_rm(match2)) return SIFT3D_FAILURE; // Populate the matrices num_matches = 0; for (i = 0; i < num; i++) { const SIFT3D_Descriptor *const desc1 = d1->buf + i; const SIFT3D_Descriptor *const desc2 = d2->buf + matches[i]; if (matches[i] == -1) continue; // Save the match SIFT3D_MAT_RM_GET(match1, num_matches, 0, double) = desc1->xd; SIFT3D_MAT_RM_GET(match1, num_matches, 1, double) = desc1->yd; SIFT3D_MAT_RM_GET(match1, num_matches, 2, double) = desc1->zd; SIFT3D_MAT_RM_GET(match2, num_matches, 0, double) = desc2->xd; SIFT3D_MAT_RM_GET(match2, num_matches, 1, double) = desc2->yd; SIFT3D_MAT_RM_GET(match2, num_matches, 2, double) = desc2->zd; num_matches++; } // Release extra memory match1->num_rows = match2->num_rows = num_matches; if (resize_Mat_rm(match1) || resize_Mat_rm(match2)) return SIFT3D_FAILURE; return SIFT3D_SUCCESS; } /* Perform nearest neighbor matching on two sets of * SIFT descriptors. * * This function will reallocate *matches. As such, *matches must be either * NULL or a pointer to previously-allocated array. Upon successful exit, * *matches is an array of size d1->num. * * On return, the ith element of matches contains the index in d2 of the match * corresponding to the ith descriptor in d1, or -1 if no match was found. * * You might consider using SIFT3D_matches_to_Mat_rm to convert the matches to * coordinate matrices. */ int SIFT3D_nn_match(const SIFT3D_Descriptor_store *const d1, const SIFT3D_Descriptor_store *const d2, const float nn_thresh, int **const matches) { int i; const int num = d1->num; // Verify inputs if (num < 1) { SIFT3D_ERR("_SIFT3D_nn_match: invalid number of " "descriptors in d1: %d \n", num); return SIFT3D_FAILURE; } // Resize the matches array (num cannot be zero) if ((*matches = (int *) SIFT3D_safe_realloc(*matches, num * sizeof(int))) == NULL) { SIFT3D_ERR("_SIFT3D_nn_match: out of memory! \n"); return SIFT3D_FAILURE; } for (i = 0; i < d1->num; i++) { // Mark -1 to signal there is no match (*matches)[i] = -1; } // Exhaustive search for matches #pragma omp parallel for for (i = 0; i < num; i++) { const SIFT3D_Descriptor *const desc1 = d1->buf + i; int *const match = *matches + i; // Forward matching pass *match = match_desc(desc1, d2, nn_thresh); // We are done if there was no match if (*match < 0) continue; // Check for forward-backward consistency if (match_desc(d2->buf + *match, d1, nn_thresh) != i) { *match = -1; } } return SIFT3D_SUCCESS; } /* Helper function to match desc against the descriptors in store. Returns the * index of the match, or -1 if none was found. */ static int match_desc(const SIFT3D_Descriptor *const desc, const SIFT3D_Descriptor_store *const store, const float nn_thresh) { const SIFT3D_Descriptor *desc_best; double ssd_best, ssd_nearest; int i; #ifdef SIFT3D_MATCH_MAX_DIST Cvec dims, dmatch; double dist_match; // Compute spatial distance rejection threshold dims.x = (float) store->nx; dims.y = (float) store->ny; dims.z = (float) store->nz; const double diag = SIFT3D_CVEC_L2_NORM(&dims); const double dist_thresh = diag * SIFT3D_MATCH_MAX_DIST; #endif // Linear search for the best and second-best SSD matches ssd_best = ssd_nearest = DBL_MAX; desc_best = NULL; for (i = 0; i < store->num; i++) { double ssd; int j; const SIFT3D_Descriptor *const desc2 = store->buf + i; // Compute the SSD of the two descriptors ssd = 0.0; for (j = 0; j < DESC_NUM_TOTAL_HIST; j++) { int a, p; const Hist *const hist1 = desc->hists + j; const Hist *const hist2 = desc2->hists + j; HIST_LOOP_START(a, p) const double diff = (double) HIST_GET(hist1, a, p) - (double) HIST_GET(hist2, a, p); ssd += diff * diff; HIST_LOOP_END // Early termination if (ssd > ssd_nearest) break; } // Compare to the best matches if (ssd < ssd_best) { desc_best = desc2; ssd_nearest = ssd_best; ssd_best = ssd; } else { ssd_nearest = SIFT3D_MIN(ssd_nearest, ssd); } } // Reject a match if the nearest neighbor is too close if (ssd_best / ssd_nearest > nn_thresh * nn_thresh) return -1; #ifdef SIFT3D_MATCH_MAX_DIST // Compute the spatial distance of the match dmatch.x = (float) desc_best->xd - desc1->xd; dmatch.y = (float) desc_best->yd - desc1->yd; dmatch.z = (float) desc_best->zd - desc1->zd; dist_match = (double) SIFT3D_CVEC_L2_NORM(&dmatch); // Reject matches of great distance if (dist_match > dist_thresh) return -1; #endif // The match was a success return desc_best - store->buf; } /* Draw the matches. * * Inputs: * -left - lefthand-side image * -right - righthand-side image * -keys_left - Keypoints from "left" (can be NULL if keys is NULL) * -keys_right - Keypoints from "right" (can be NULL if keys is NULL) * -match_left - Matches from "left" (can be NULL if lines is NULL) * -match_right - Matches from "right" (can be NULL if lines is NULL) * * Outputs: * -concat - Concatenated image (NULL if not needed) * -keys - Keypoints from concat (NULL is not needed) * -lines - Lines between matching keypoints in concat (NULL if not needed) * It is an error if all outputs are NULL. * * Return: * SIFT3D_SUCCESS or SIFT3D_FAILURE */ int draw_matches(const Image *const left, const Image *const right, const Mat_rm *const keys_left, const Mat_rm *const keys_right, const Mat_rm *const match_left, const Mat_rm *const match_right, Image *const concat, Image *const keys, Image *const lines) { Image concat_temp, left_padded, right_padded; Mat_rm keys_right_draw, keys_left_draw, keys_draw, match_right_draw; int i; const double right_pad = (double) left->nx; const int ny_pad = SIFT3D_MAX(right->ny, left->ny); const int nz_pad = SIFT3D_MAX(right->nz, left->nz); // Choose which image to use for concatenation Image *const concat_arg = concat == NULL ? &concat_temp : concat; // Verify inputs if (concat == NULL && keys == NULL && lines == NULL) { SIFT3D_ERR("draw_matches: all outputs are NULL \n"); return SIFT3D_FAILURE; } if (keys_left == NULL && keys != NULL) { SIFT3D_ERR("draw_matches: keys_left is NULL but keys is " "not \n"); return SIFT3D_FAILURE; } if (keys_right == NULL && keys != NULL) { SIFT3D_ERR("draw_matches: keys_right is NULL but keys is " "not \n"); return SIFT3D_FAILURE; } if (match_left == NULL && lines != NULL) { SIFT3D_ERR("draw_matches: match_left is NULL but lines " "is not \n"); return SIFT3D_FAILURE; } if (match_right == NULL && lines != NULL) { SIFT3D_ERR("draw_matches: match_right is NULL but lines " "is not \n"); return SIFT3D_FAILURE; } // Initialize intermediates init_im(&concat_temp); init_im(&left_padded); init_im(&right_padded); if (init_Mat_rm(&keys_right_draw, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&match_right_draw, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&keys_left_draw, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&keys_draw, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Pad the images to be the same in all dimensions but x if (init_im_with_dims(&right_padded, right->nx, ny_pad, nz_pad, 1) || init_im_with_dims(&left_padded, left->nx, ny_pad, nz_pad, 1) || im_pad(right, &right_padded) || im_pad(left, &left_padded)) { SIFT3D_ERR("draw_matches: unable to pad images \n"); return SIFT3D_FAILURE; } // Draw a concatenated image if (im_concat(&left_padded, &right_padded, 0, concat_arg)) { SIFT3D_ERR("draw_matches: Could not concatenate the " "images \n"); goto draw_matches_quit; } // Optionally draw the keypoints if (keys != NULL) { // Convert inputs to double if (convert_Mat_rm(keys_right, &keys_right_draw, SIFT3D_DOUBLE) || convert_Mat_rm(keys_left, &keys_left_draw, SIFT3D_DOUBLE)) goto draw_matches_quit; // Pad the x-coordinate for (i = 0; i < keys_right->num_rows; i++) { SIFT3D_MAT_RM_GET(&keys_right_draw, i, 0, double) += right_pad; } // Concatenate the points if (concat_Mat_rm(&keys_left_draw, &keys_right_draw, &keys_draw, 0)) goto draw_matches_quit; // Draw the points if (draw_points(&keys_draw, SIFT3D_IM_GET_DIMS(concat_arg), 1, keys)) goto draw_matches_quit; } // Optionally draw the lines if (lines != NULL) { // Convert input to double if (convert_Mat_rm(match_right, &match_right_draw, SIFT3D_DOUBLE)) goto draw_matches_quit; // Pad the x-coordinate for (i = 0; i < match_right->num_rows; i++) { SIFT3D_MAT_RM_GET(&match_right_draw, i, 0, double) += right_pad; } // Draw the lines if (draw_lines(match_left, &match_right_draw, SIFT3D_IM_GET_DIMS(concat_arg), lines)) goto draw_matches_quit; } // Clean up im_free(&concat_temp); im_free(&left_padded); im_free(&right_padded); cleanup_Mat_rm(&keys_right_draw); cleanup_Mat_rm(&keys_left_draw); cleanup_Mat_rm(&keys_draw); cleanup_Mat_rm(&match_right_draw); return SIFT3D_SUCCESS; draw_matches_quit: im_free(&concat_temp); im_free(&left_padded); im_free(&right_padded); cleanup_Mat_rm(&keys_right_draw); cleanup_Mat_rm(&keys_left_draw); cleanup_Mat_rm(&keys_draw); cleanup_Mat_rm(&match_right_draw); return SIFT3D_FAILURE; } /* Write a Keypoint_store to a text file. The keypoints are stored in a matrix * (.csv, .csv.gz), where each keypoint is a row. The elements of each row are * as follows: * * x y z o s ori11 ori12 ... orinn * * x - the x-coordinate * y - the y-coordinate * z - the z-coordinate * o - the pyramid octave. To convert to image coordinates, multiply x,y,z by * pow(2, o) * s - the scale coordinate * ori(ij) - the ith row, jth column of the orientation matrix */ int write_Keypoint_store(const char *path, const Keypoint_store *const kp) { Mat_rm mat; int i, i_R, j_R; // Keypoint data format constants const int kp_x = 0; // column of x-coordinate const int kp_y = 1; // column of y-coordinate const int kp_z = 2; // column of z-coordinate const int kp_o = 3; // column of octave index const int kp_s = 4; // column of s-coordinate const int kp_ori = 5; // first column of the orientation matrix const int ori_numel = IM_NDIMS * IM_NDIMS; // Number of orientation // elements const int num_rows = kp->slab.num; const int num_cols = kp_ori + ori_numel; // Initialize the matrix if (init_Mat_rm(&mat, num_rows, num_cols, SIFT3D_DOUBLE, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Write the keypoints for (i = 0; i < num_rows; i++) { const Keypoint *const key = kp->buf + i; const Mat_rm *const R = &key->R; // Write the coordinates SIFT3D_MAT_RM_GET(&mat, i, kp_x, double) = key->xd; SIFT3D_MAT_RM_GET(&mat, i, kp_y, double) = key->yd; SIFT3D_MAT_RM_GET(&mat, i, kp_z, double) = key->zd; SIFT3D_MAT_RM_GET(&mat, i, kp_o, double) = key->o; SIFT3D_MAT_RM_GET(&mat, i, kp_s, double) = key->sd; // Write the orientation matrix SIFT3D_MAT_RM_LOOP_START(R, i_R, j_R) const int kp_idx = kp_ori + SIFT3D_MAT_RM_GET_IDX(R, i_R, j_R); SIFT3D_MAT_RM_GET(&mat, i, kp_idx, double) = (double) SIFT3D_MAT_RM_GET(R, i_R, j_R, float); SIFT3D_MAT_RM_LOOP_END } // Write the matrix if (write_Mat_rm(path, &mat)) goto write_kp_quit; // Clean up cleanup_Mat_rm(&mat); return SIFT3D_SUCCESS; write_kp_quit: cleanup_Mat_rm(&mat); return SIFT3D_FAILURE; } /* Write SIFT3D descriptors to a text file. * See SIFT3D_Descriptor_store_to_Mat_rm for the file format. */ int write_SIFT3D_Descriptor_store(const char *path, const SIFT3D_Descriptor_store *const desc) { Mat_rm mat; // Initialize the matrix if (init_Mat_rm(&mat, 0, 0, SIFT3D_FLOAT, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Write the data into the matrix if (SIFT3D_Descriptor_store_to_Mat_rm(desc, &mat)) goto write_desc_quit; // Write the matrix to the file if (write_Mat_rm(path, &mat)) goto write_desc_quit; // Clean up cleanup_Mat_rm(&mat); return SIFT3D_SUCCESS; write_desc_quit: cleanup_Mat_rm(&mat); return SIFT3D_FAILURE; } ================================================ FILE: sift3d/sift.h ================================================ /* ----------------------------------------------------------------------------- * sift.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Public header for sift.c * ----------------------------------------------------------------------------- */ #include "imtypes.h" #ifndef _SIFT_H #define _SIFT_H #ifdef __cplusplus extern "C" { #endif void init_Keypoint_store(Keypoint_store *const kp); int init_Keypoint(Keypoint *const key); int resize_Keypoint_store(Keypoint_store *const kp, const size_t num); int copy_Keypoint(const Keypoint *const src, Keypoint *const dst); void cleanup_Keypoint_store(Keypoint_store *const kp); void init_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc); void cleanup_SIFT3D_Descriptor_store(SIFT3D_Descriptor_store *const desc); int set_peak_thresh_SIFT3D(SIFT3D *const sift3d, const double peak_thresh); int set_corner_thresh_SIFT3D(SIFT3D *const sift3d, const double corner_thresh); int set_num_kp_levels_SIFT3D(SIFT3D *const sift3d, const unsigned int num_kp_levels); int set_sigma_n_SIFT3D(SIFT3D *const sift3d, const double sigma_n); int set_sigma0_SIFT3D(SIFT3D *const sift3d, const double sigma_n); int init_SIFT3D(SIFT3D *sift3d); int copy_SIFT3D(const SIFT3D *const src, SIFT3D *const dst); void cleanup_SIFT3D(SIFT3D *const sift3d); void print_opts_SIFT3D(void); int parse_args_SIFT3D(SIFT3D *const sift3d, const int argc, char **argv, const int check_err); int SIFT3D_assign_orientations(const SIFT3D *const sift3d, const Image *const im, Keypoint_store *const kp, double **const conf); int SIFT3D_detect_keypoints(SIFT3D *const sift3d, const Image *const im, Keypoint_store *const kp); int SIFT3D_have_gpyr(const SIFT3D *const sift3d); int SIFT3D_extract_descriptors(SIFT3D *const sift3d, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc); int SIFT3D_extract_raw_descriptors(SIFT3D *const sift3d, const Image *const im, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc); int SIFT3D_extract_dense_descriptors(SIFT3D *const sift3d, const Image *const in, Image *const desc); int SIFT3D_nn_match(const SIFT3D_Descriptor_store *const d1, const SIFT3D_Descriptor_store *const d2, const float nn_thresh, int **const matches); int Keypoint_store_to_Mat_rm(const Keypoint_store *const kp, Mat_rm *const mat); int SIFT3D_Descriptor_coords_to_Mat_rm( const SIFT3D_Descriptor_store *const store, Mat_rm *const mat); int SIFT3D_Descriptor_store_to_Mat_rm(const SIFT3D_Descriptor_store *const store, Mat_rm *const mat); int Mat_rm_to_SIFT3D_Descriptor_store(const Mat_rm *const mat, SIFT3D_Descriptor_store *const store); int SIFT3D_matches_to_Mat_rm(SIFT3D_Descriptor_store *d1, SIFT3D_Descriptor_store *d2, const int *const matches, Mat_rm *const match1, Mat_rm *const match2); int draw_matches(const Image *const left, const Image *const right, const Mat_rm *const keys_left, const Mat_rm *const keys_right, const Mat_rm *const match_left, const Mat_rm *const match_right, Image *const concat, Image *const keys, Image *const lines); int write_Keypoint_store(const char *path, const Keypoint_store *const kp); int write_SIFT3D_Descriptor_store(const char *path, const SIFT3D_Descriptor_store *const desc); #ifdef __cplusplus } #endif #endif ================================================ FILE: wrappers/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for the wrappers for other programming languages. ################################################################################ # Subdirectories for each language if (BUILD_Matlab) add_subdirectory (matlab) endif () ================================================ FILE: wrappers/matlab/CMakeLists.txt ================================================ ################################################################################ # Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ################################################################################ # Build file for the Matlab wrapper toolbox. ################################################################################ # There is an issue with MEX compilation for Matlab 2018b that requires a newer # CMake. cmake_minimum_required (VERSION 3.15) # Enumerate the Matlab scripts for testing and installation set (INSTALL_SCRIPTS setupSift3D.m imRead3D.m imWrite3D.m detectSift3D.m extractSift3D.m orientation3D.m keypoint3D.m registerSift3D.m matchSift3D.m checkUnits3D.m Sift3DParser.m) set (TEST_SCRIPTS Sift3DTest.m) # Build the mex utility library add_library (mexutil SHARED mexutil.c) target_include_directories (mexutil PUBLIC ${Matlab_INCLUDE_DIRS}) target_include_directories (mexutil PUBLIC $ $ ) target_link_libraries (mexutil PUBLIC mexreg mexsift3D meximutil ${Matlab_LIBRARIES}) # Build the mex files matlab_add_mex (NAME mexImRead3D SRC mexImRead3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexImWrite3D SRC mexImWrite3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexDetectSift3D SRC mexDetectSift3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexOrientation3D SRC mexOrientation3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexExtractSift3D SRC mexExtractSift3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexRegisterSift3D SRC mexRegisterSift3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) matlab_add_mex (NAME mexMatchSift3D SRC mexMatchSift3D.c LINK_TO mexutil ${Matlab_LIBRARIES} ) # Enumerate the binary targets set (BINARIES mexutil mexImRead3D mexImWrite3D mexDetectSift3D mexOrientation3D mexExtractSift3D mexRegisterSift3D mexMatchSift3D) # Configure the build destination set_target_properties (${BINARIES} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} LIBRARY_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} RUNTIME_OUTPUT_DIRECTORY ${BUILD_TOOLBOX_DIR} ) # Copy the scripts and test suite to the build tree foreach (FILENAME IN LISTS INSTALL_SCRIPTS TEST_SCRIPTS) configure_file (${FILENAME} "${BUILD_TOOLBOX_DIR}/${FILENAME}" COPYONLY) endforeach () # Install the matlab scripts install (FILES ${INSTALL_SCRIPTS} DESTINATION ${INSTALL_TOOLBOX_DIR} ) # Install the libraries and mex files install (TARGETS ${BINARIES} RUNTIME DESTINATION ${INSTALL_TOOLBOX_DIR} LIBRARY DESTINATION ${INSTALL_TOOLBOX_DIR} ARCHIVE DESTINATION ${INSTALL_TOOLBOX_DIR} ) ================================================ FILE: wrappers/matlab/README.md ================================================ # SIFT3D for Matlab Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. ## Contents This is a Matlab toolbox wrapping SIFT3D. It contains the following functions: - detectSift3D.m - Detect SIFT3D keypoints from a 3-dimensional array. - extractSift3D.m - Extract SIFT3D descriptors from a 3-dimensional array. - keypoint3D.m - Create SIFT3D keypoints from user-supplied coordinates. - orientation3D.m - Assign 3D orientations to user-supplied keypoints. - registerSift3D.m - Register images using SIFT3D keypoints and descriptors. - imRead3D.m - Read 2D and 3D images in DICOM and NIFTI formats. - imWrite3D.m - Write 2D and 3D images in DICOM and NIFTI formats. ## Installation instructions If you installed SIFT3D from binaries, a Matlab toolbox is included in the lib/wrappers/matlab subdirectory of your installation. If you compiled SIFT3D from source, the toolbox will be built only if Matlab was detected in your system. To use the toolbox, simply add it to your Matlab path. This can be accomplished by adding the following line to your startup.m file: run('/path/to/sift3d/lib/wrappers/matlab/setupSift3D') where /path/to/sift3d is the path to your SIFT3D installation. If you do not have a startup.m file, you can simply run this command prior to calling any SIFT3D functions. ### Relocating the toolbox We do not recommend moving the toolbox shared libraries (.so, .dylib, .dll). If you do, the operating system may not be able to find them when it tries to load the mex files. A better solution is to install SIFT3D in the place you want the toolbox to reside. ### Troubleshooting When compiling from source, CMake might fail to find Matlab on your system, especially on Mac OSX. In that case, you should see "Matlab not found" after running the cmake command. You can fix this by manually specifying the location of Matlab in the variable Matlab_ROOT_DIR. For example, cmake .. -DMatlab_ROOT_DIR=/path/to/MATLAB or on Mac OSX, cmake .. -DMatlab_ROOT_DIR=/path/to/Matlab.app where /path/to/MATLAB is the location of your Matlab installation. ## Usage instructions For instructions on using the Matlab functions, use the help pages, e.g. help detectSift3D See /examples for sample programs. ## Advanced features This toolbox also includes a test suite, Sift3DTest.m. The test suite is found only in the source distribution, not the binary distributions, and must be run from the build tree. It requires [xUnit](http://www.mathworks.com/matlabcentral/fileexchange/22846-matlab-xunit-test-framework) to run. You can run the test suite with the following command: runtests ================================================ FILE: wrappers/matlab/Sift3DParser.m ================================================ classdef Sift3DParser < inputParser %Sift3DParser helper class to parse SIFT3D options. This class inherits % from inputParser. Call its 'parseAndVerify' method to parse the input % string. The results will be stored in the 'Results' field. Additional % options can be added just as in inputParser. properties (SetAccess = private) % Option names peakThreshStr = 'peakThresh'; cornerThreshStr = 'cornerThresh'; numKpLevelsStr = 'numKpLevels'; sigmaNStr = 'sigmaN'; sigma0Str = 'sigma0'; end methods % Constructor function self = Sift3DParser % Call the parent constructor self = self@inputParser; % Add the SIFT3D options self.addParamValue(self.peakThreshStr, []) self.addParamValue(self.cornerThreshStr, []) self.addParamValue(self.numKpLevelsStr, []) self.addParamValue(self.sigmaNStr, []) self.addParamValue(self.sigma0Str, []) end % Verify and retrieve the SIFT3D options function optStruct = parseAndVerify(self, varargin) % Parse the input self.parse(varargin{:}) % Retrieve the results peakThresh = self.Results.peakThresh; cornerThresh = self.Results.cornerThresh; numKpLevels = self.Results.numKpLevels; sigmaN = self.Results.sigmaN; sigma0 = self.Results.sigma0; % Verify the results if ~isempty(peakThresh) validateattributes(peakThresh, {'numeric'}, ... {'real', 'positive', 'scalar', '<=', 1}, 'peakThresh') end if ~isempty(cornerThresh) validateattributes(cornerThresh, {'numeric'}, ... {'real', 'nonnegative', 'scalar', '<=', 1}, ... 'cornerThresh') end if ~isempty(numKpLevels) validateattributes(numKpLevels, {'numeric'}, ... {'real', 'integer', 'scalar', 'positive'}, ... 'numKpLevels') end if ~isempty(sigmaN) validateattributes(sigmaN, {'numeric'}, ... {'real', 'positive', 'scalar'}, 'sigmaN') end if ~isempty(sigma0) validateattributes(sigma0, {'numeric'}, ... {'real', 'positive', 'scalar'}, 'sigma0') end if sigmaN >= sigma0 error('Cannot have sigmaN >= sigma0') end % Collect the options in a struct optStruct = struct( ... self.peakThreshStr, peakThresh, ... self.cornerThreshStr, cornerThresh, ... self.numKpLevelsStr, numKpLevels, ... self.sigmaNStr, sigmaN, ... self.sigma0Str, sigma0); end end end ================================================ FILE: wrappers/matlab/Sift3DTest.m ================================================ classdef Sift3DTest < TestCase %Sift3DTest a test suite for SIFT3D. % % To run this test suite, you must install xUnit. As of January % 12th, 2017, xUnit is available at: % http://www.mathworks.com/matlabcentral/fileexchange/47302-xunit4 % % Run the tests with the following command: % runxunit % % This test suite can only be run from the build tree. % % Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. properties (SetAccess = private) cd buildDir binDir examplesDir im1Name im2Name dataName kpCmd regCmd tolText fullTest end methods % Constructor function self = Sift3DTest(name) % Call the parent constructor self = self@TestCase(name); % Current directory self.cd = fileparts(mfilename('fullpath')); % Build directory self.buildDir = fullfile(self.cd, '..', '..', '..'); % Binary directory self.binDir = fullfile(self.buildDir, 'bin'); % Examples directory if ispc self.examplesDir = self.binDir; else self.examplesDir = fullfile(self.buildDir, 'examples'); end % Image file names self.im1Name = fullfile(self.examplesDir, '1.nii.gz'); self.im2Name = fullfile(self.examplesDir, '2.nii.gz'); % Keypoints command name self.kpCmd = fullfile(self.binDir, 'kpSift3D'); % Registration command name self.regCmd = fullfile(self.binDir, 'regSift3D'); % Error tolerance for text output self.tolText = 0.01; % Run the tests on real data (slow) self.fullTest = true; end % Test keypoint detection against the CLI version function detectCliTest(self) if ~self.fullTest || ispc return end % Output file name kpCliName = 'kpCli.csv'; % Detect keypoints using the command line interface status = runCmd([self.kpCmd ' --keys ' kpCliName ' ' ... self.im1Name]); assertEqual(status, 0); % Load the CLI keypoints kpCli = csvread(kpCliName); % Load the image data [im1, units1] = imRead3D(self.im1Name); % Detect keypoints using matlab keys = detectSift3D(im1, 'units', units1); % Check the dimensions assertEqual(size(kpCli, 1), length(keys)); assertEqual(size(kpCli, 2), numel(keys(1).coords) + ... numel(keys(1).octave) + numel(keys(1).scale) + ... numel(keys(1).ori)); % Compare the two for i = 1 : length(keys) mKey = keys(i); cliKey = kpCli(i, :); % Check the coordinates assertElementsAlmostEqual(mKey.coords, cliKey(1:3), ... 'absolute', self.tolText); % Check the octave assertEqual(mKey.octave, cliKey(4)); % Check the scale assertElementsAlmostEqual(mKey.scale, cliKey(5), ... 'absolute', self.tolText); % Check the orientation assertElementsAlmostEqual(mKey.ori, ... reshape(cliKey(6:end), size(mKey.ori))', ... 'absolute', self.tolText); end % Clean up delete(kpCliName); end % Test descriptor extraction against the CLI version function extractCliTest(self) if ~self.fullTest || ispc return end % Output file name descCliName = 'descCli.csv'; % Extract descriptors using the command line interface status = runCmd([self.kpCmd ' --desc ' descCliName ' ' ... self.im1Name]); assertEqual(status, 0); % Read the results descCli = csvread(descCliName); % Load the image data [im1, units1] = imRead3D(self.im1Name); % Extract descriptors using matlab keys = detectSift3D(im1, 'units', units1); [desc, coords] = extractSift3D(keys); % Check the dimensions assertEqual(size(desc, 1), size(coords, 1)); assertEqual(size(descCli, 1), size(desc, 1)); assertEqual(size(descCli, 2), size(desc, 2) + size(coords, 2)); % Compare the two for i = 1 : length(keys) cliDescrip = descCli(i, :); % Check the coordinates assertElementsAlmostEqual(cliDescrip(1 : 3), ... coords(i, :), 'absolute', self.tolText); % Check the descriptor assertElementsAlmostEqual(cliDescrip(4 : end), ... desc(i, :), 'absolute', self.tolText); end % Clean up delete(descCliName); end % Test that "raw" image descriptors are close to those extracted % from a Gaussian scale-space pyramid function rawDescriptorTest(self) if ~self.fullTest return end % Load the image data [im1, units1] = imRead3D(self.im1Name); % Detect keypoints keys = detectSift3D(im1, 'units', units1); % Extract descriptors using the pyramid [descPyr, coordsPyr] = extractSift3D(keys); % Extract raw descriptors [descRaw, coordsRaw] = extractSift3D(keys, im1, units1); % Check the results assertElementsAlmostEqual(coordsPyr, coordsRaw); assertElementsAlmostEqual(descPyr, descRaw, 'absolute', 0.2); end % Test that "raw" keypoint orientations are close to those % extracted from a Gaussian scale-space pyramid function rawOrientationTest(self) if ~self.fullTest return end % Load the image data [im, units] = imRead3D(self.im1Name); % Detect keypoints keys = detectSift3D(im, 'units', units); % Assign orientations to those same keypoints keysRaw = orientation3D(keys, im, units); % Check the dimensions assertEqual(size(keys), size(keysRaw)); % Create a basis vector u = zeros(length(keys(1).ori), 1); u(1) = 1; % Check the results ang = zeros(length(keys), 1); for i = 1 : length(keys) key = keys(i); keyRaw = keysRaw(i); % Rotate the basis vector uRotKey = key.ori * u; uRotKeyRaw = keyRaw.ori * u; % Compute the angle between the rotated vectors ang(i) = acos(abs(dot(uRotKey, uRotKeyRaw))); end % Test the median angle assertElementsAlmostEqual(median(ang), 0, 'absolute', pi / 8); end % Test that detected keypoints are valid function detectValidTest(self) if ~self.fullTest return end % Load the image data [im, units] = imRead3D(self.im1Name); % Extract keypoints keys = detectSift3D(im, 'units', units); % Check the keypoints for i = 1 : length(keys) key = keys(i); % Check containment in the original image baseCoords = key.coords * pow2(-key.octave); assertTrue(all(baseCoords >= 0)); assertTrue(all(baseCoords < size(im))); % Check orthogonality of the rotation matrix assertElementsAlmostEqual(key.ori * key.ori', ... eye(length(key.ori)), 'absolute', 1E-3); % Check determinant of the rotation matrix assertElementsAlmostEqual(det(key.ori), 1, 'absolute', ... 1E-3); end end % Test registering an image against the CLI version function regCliTest(self) if ~self.fullTest || ispc return end % Output file names matchesName = 'matches.csv'; transformName = 'transform.csv'; % Register with the CLI status = runCmd([self.regCmd ' --matches ' matchesName ... ' --transform ' transformName ' ' self.im1Name ' ' ... self.im2Name]); assertEqual(status, 0); % Read the results matchesCli = csvread(matchesName); transformCli = csvread(transformName); % Convert the results to Matlab's format matchSrcCli = matchesCli(:, 1 : 3); matchRefCli = matchesCli(:, 4 : end); % Load the images [im1, units1] = imRead3D(self.im1Name); [im2, units2] = imRead3D(self.im2Name); % Register with Matlab [A, matchSrc, matchRef] = registerSift3D(im1, im2, ... 'srcUnits', units1, 'refUnits', units2); % Check the dimensions assertEqual(size(A), size(transformCli)); assertEqual(size(matchSrc), size(matchSrcCli)); assertEqual(size(matchRef), size(matchRefCli)); % Check the matches (the only error is conversion to text) assertElementsAlmostEqual(matchSrc, matchSrcCli, ... 'absolute', self.tolText); assertElementsAlmostEqual(matchRef, matchRefCli, ... 'absolute', self.tolText); % Check the transformation (discrepancies introduced by % randomized regression) assertElementsAlmostEqual(A(:, 1 : 3), ... transformCli(:, 1 : 3), 'absolute', 5E-2); assertElementsAlmostEqual(A(:, end), transformCli(:, end), ... 'absolute', 5); % Clean up delete(matchesName); delete(transformName); end % Test anisotropic registration function regAnisoTest(self) if ~self.fullTest return end % Load the image [im, units] = imRead3D(self.im1Name); % Remove half of the slices of the image imAniso = im(:, :, 1 : 2 : end); unitsAniso = [units(1) units(2) units(3) * 2]; % Register the original to the anisotropic image A = registerSift3D(im, imAniso, 'srcUnits', units, ... 'refUnits', unitsAniso, 'resample', true); % Form the reference (ground truth) transformation refA = [eye(3) zeros(3, 1)]; refA(3, 3) = 2; % Check the transformation assertElementsAlmostEqual(A(:, 1 : 3), refA(:, 1 : 3), ... 'absolute', 5E-2); assertElementsAlmostEqual(A(:, end), refA(:, end), ... 'absolute', 5); end % Test matching descriptors against the C version function matchTest(self) if ~self.fullTest return end % Load the images [im1, units1] = imRead3D(self.im1Name); [im2, units2] = imRead3D(self.im2Name); % Register with the C version and get the matches [~, match1, match2] = registerSift3D(im1, im2, ... 'srcUnits', units1, 'refUnits', units2); % Extract descriptors from each image keys = detectSift3D(im1, 'units', units1); [desc1, coords1] = extractSift3D(keys); keys = detectSift3D(im2, 'units', units2); [desc2, coords2] = extractSift3D(keys); % Match with the Matlab version and convert to coordinates matches = matchSift3D(desc1, coords1, desc2, coords2); match1M = coords1(matches(:, 1), :); match2M = coords2(matches(:, 2), :); assertElementsAlmostEqual(match1M, match1, 'relative', 1E-3); assertElementsAlmostEqual(match2M, match2, 'relative', 1E-3); end % Test registration with invalid matching threshold function regInvalidMatchTest(self) % Load the images im1 = imRead3D(self.im1Name); im2 = imRead3D(self.im2Name); threwErr = false; try A = registerSift3D(im1, im2, 'nnThresh', 2); catch ME threwErr = true; end assertTrue(threwErr); end % Test registration with invalid error threshold function regInvalidErrTest(self) % Load the images im1 = imRead3D(self.im1Name); im2 = imRead3D(self.im2Name); threwErr = false; try A = registerSift3D(im1, im2, 'errThresh', -1); catch ME threwErr = true; end assertTrue(threwErr); end % Test registration with invalid number of iterations function regInvalidIterTest(self) % Load the images im1 = imRead3D(self.im1Name); im2 = imRead3D(self.im2Name); threwErr = false; try A = registerSift3D(im1, im2, 'numIter', 0); catch ME threwErr = true; end assertTrue(threwErr); end % Test reading and writing a NIFTI image function niftiIOTest(self) % The temporary file name imName = 'temp.nii.gz'; % Make random image data imWritten = rand(10, 15, 20); % Write the image as a NIFTI file imWrite3D(imName, imWritten); % Read the image back imRead = imRead3D(imName); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(imWritten, imRead, 'relative', 1E-3); end % Test reading and writing a DICOM image function dicomIOTest(self) % The temporary file name imName = 'temp.dcm'; % Remove any past instances of this file if exist(imName, 'file') delete(imName) end % Make random image data, scaled to the range [0, 1] imWritten = rand(10, 15, 20); imWritten = imWritten / max(imWritten(:)); % Write the image as a DICOM file imWrite3D(imName, imWritten); % Read the image back and scale it imRead = imRead3D(imName); imRead = imRead / max(imRead(:)); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(imWritten, imRead, 'absolute', 1E-2); end % Test reading and writing a directory of DICOM images function dirIOTest(self) % The temporary file name dirName = 'temp'; % Make random image data, scaled to the range [0, 1] imWritten = rand(10, 15, 20); imWritten = imWritten / max(imWritten(:)); % Write the image as a DICOM file imWrite3D(dirName, imWritten); % Read the image back and scale it imRead = imRead3D(dirName); imRead = imRead / max(imRead(:)); % Clean up rmdir(dirName, 's'); % Ensure the results are identical assertElementsAlmostEqual(imWritten, imRead, 'absolute', 1E-2); end % Test reading and writing a 2D NIFTI image function nifti2DTest(self) % The temporary file name imName = 'temp.nii.gz'; % Make random image data imWritten = rand(20, 15); % Write the image as a NIFTI file imWrite3D(imName, imWritten); % Read the image back imRead = imRead3D(imName); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(imWritten, imRead, 'relative', 1E-3); end % Test reading and writing a 2D DICOM image function dicom2DTest(self) % The temporary file name imName = 'temp.dcm'; % Make random image data, scaled to the range [0, 1] imWritten = rand(20, 15); imWritten = imWritten / max(imWritten(:)); % Write the image as a DICOM file imWrite3D(imName, imWritten); % Read the image back and scale it imRead = imRead3D(imName); imRead = imRead / max(imRead(:)); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(imWritten, imRead, 'absolute', 1E-2); end % Test reading and writing units from a NIFTI image function niftiUnitsTest(self) % The temporary file name imName = 'temp.nii.gz'; % Make random image data imWritten = rand(10, 15, 20); % Make random units unitsWritten = rand(3, 1); % Write the image as a NIFTI file imWrite3D(imName, imWritten, unitsWritten); % Read the units back [~, unitsRead] = imRead3D(imName); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(unitsWritten, unitsRead, ... 'relative', 1E-3); end % Test reading and writing units from a DICOM image function dicomUnitsTest(self) % The temporary file name imName = 'temp.dcm'; % Make random image data imWritten = rand(10, 15, 20); % Make random units unitsWritten = rand(3, 1); % Write the image as a NIFTI file imWrite3D(imName, imWritten, unitsWritten); % Read the units back [~, unitsRead] = imRead3D(imName); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(unitsWritten, unitsRead, ... 'relative', 1E-3); end % Test reading and writing units from a directory of DICOM images function dirUnitsTest(self) % The temporary file name imName = 'temp'; % Make random image data imWritten = rand(10, 15, 20); % Make random units unitsWritten = rand(3, 1); % Write the image as a NIFTI file imWrite3D(imName, imWritten, unitsWritten); % Read the units back [~, unitsRead] = imRead3D(imName); % Clean up rmdir(imName, 's'); % Ensure the results are identical assertElementsAlmostEqual(unitsWritten, unitsRead, ... 'relative', 1E-3); end % Test reading and writing units from a 2D DICOM image function units2DTest(self) % The temporary file name imName = 'temp.dcm'; % Make random image data imWritten = rand(20, 15); % Make random units unitsWritten = rand(2, 1); % Write the image as a NIFTI file imWrite3D(imName, imWritten, unitsWritten); % Read the image back [~, unitsRead] = imRead3D(imName); % Remove the trailing units unitsRead = unitsRead(1 : length(unitsWritten)); % Clean up delete(imName); % Ensure the results are identical assertElementsAlmostEqual(unitsWritten, unitsRead, ... 'relative', 1E-3); end % Test switching the parameter order in imWrite3D function writeSwappedParamsTest(self) % Make a random image im = rand(10, 10, 10); % Try to write it, with parameters exchanged threwErr = false; try imWrite3D(im, 'im.nii.gz'); catch ME threwErr = true; end assertTrue(threwErr); end % Test reading an invalid filetype function readInvalidTypeTest(self) % Temporary file name tempFileName = 'temp.mat'; % Make a temporary file threwErr = false; save(tempFileName); % Try to read it try im = imRead3D(tempFileName); catch ME threwErr = true; end % Clean up delete(tempFileName); assertTrue(threwErr); end % Test writing an invalid filetype function writeInvalidTypeTest(self) im = rand(10, 10, 10, 10); threwErr = false; try imWrite3D(im, 'bogus.txt') catch ME threwErr = true; end assertTrue(threwErr); end % Test reading from a nonexistent file function imReadNonexistentTest(self) threwErr = false; try im = imRead3D('nonexistent.nii.gz'); catch ME threwErr = true; end assertTrue(threwErr); end % Test writing negative units function negativeUnitsTest(self) % Invalid units units = [1 1 -1]; % Fake image im = zeros(10, 10, 10); threwErr = false; try imWrite3D('fake.nii.gz', im, units); catch ME threwErr = true; end assertTrue(threwErr); end % Test writing zero-valued units function zeroUnitsTest(self) % Invalid units units = [1 0 1]; % Fake image im = zeros(10, 10, 10); threwErr = false; try imWrite3D('fake.nii.gz', im, units); catch ME threwErr = true; end assertTrue(threwErr); end % Test making valid keypoints function keypointValidTest(self) % Make the keypoints coords = [1 1 1; 2 2 2]; scale = [1 2]; ori = repmat(rotMat(1), [1 1 length(scale)]); keys = keypoint3D(coords, scale, ori); % Test the results for i = 1 : length(keys) key = keys(i); assertEqual(key.coords, coords(i, :)); assertEqual(key.scale, scale(i)); assertEqual(key.ori, ori(:, :, i)); end end % Test making keypoints with invalid coordinate dimensions function keypointInvalidCoordTest(self) % Make the keypoints coords = [1 1 1 1]; % Test the results threwErr = false; try keypoint3D(coords); catch ME threwErr = true; end assertTrue(threwErr); end % Test making keypoints with invalid scale dimensions function keypointInvalidScaleTest(self) % Make the keypoints coords = [1 1 1; 2 2 2]; scale = [1 1; 2 2]; % Test the results threwErr = false; try keypoint3D(coords, scale); catch ME threwErr = true; end assertTrue(threwErr); end % Test making keypoints with invalid rotation matrix dimensions function keypointInvalidRotDimsTest(self) % Make the keypoints coords = [1 1 1; 2 2 2]; ori = repmat(ones(3, 4), [1 1 size(coords, 1)]); % Test the results threwErr = false; try keypoint3D(coords, [], ori); catch ME threwErr = true; end assertTrue(threwErr); end % Test making a keypoint with a reflection matrix function keypointReflectTest(self) % Make the keypoints coords = [1 1 1]; ori = eye(3); ori(1, 1) = -1; % Test the results threwErr = false; try keypoint3D(coords, [], ori); catch ME threwErr = true; end assertTrue(threwErr); end % Test making a keypoint with a non-orthogonal matrix function keypointOrthTest(self) % Make the keypoints coords = [1 1 1]; ori = rand(3); % Ensure that the determinant is equal to one, so it is % orthogonality and not reflection which causes an error detOri = det(ori); factor = sign(detOri) * abs(detOri) ^ (-1 / length(ori)); ori = ori * factor; assert(abs(det(ori) - 1) < eps * 1E2); % Test the results threwErr = false; try keypoint3D(coords, [], ori); catch ME threwErr = true; end assertTrue(threwErr); end end end function status = runCmd(cmd) %runCmd helper function to run a command in the default system environment, % without Matlab's changes to the environment variables % The CLI is not supported on Windows if ispc warning(['The command-line interface is not supported in the ' ... 'Windows version of SIFT3D']) end % Strip the LD_LIBRARY_PATH environment variable of Matlab % directories ldPathVar = 'LD_LIBRARY_PATH'; oldLdPath = getenv(ldPathVar); newLdPath = regexprep(oldLdPath, '[^:]*MATLAB[^:]*:*', '', 'ignorecase'); setenv(ldPathVar, newLdPath); % Run the command status = system(cmd); % Return the environment to its previous state setenv(ldPathVar, oldLdPath); end function R = rotMat(theta) %rotMat Helper function to make a 3D rotation matrix, rotating by angle % theta in the XY plane R = eye(3); R(1, 1) = cos(theta); R(1, 2) = -sin(theta); R(2, 1) = sin(theta); R(2, 2) = cos(theta); end ================================================ FILE: wrappers/matlab/checkUnits3D.m ================================================ function units = checkUnits3D(units, name) %checkUnits3D helper function to check the validity of the 'units' argument % to various SIFT3D functions. Returns a properly formatted version of % units, with missing values set to 1. % % The optional 'name' argument is used in error messages. % Default arguments if nargin < 1 || isempty(units) units = ones(3, 1); end if nargin < 2 || isempty(name) name = 'units'; end % Number of image dimensions ndim = 3; % Validate units validateattributes(units, {'numeric'}, {'real', 'vector', 'positive'}, ... name) if length(units) > ndim error([name ' must be a [3x1] real numeric vector']) end % Convert to double units = double(units); % Default for missing values if length(units) < ndim units(length(units) + 1 : ndim) = 1; end % Transpose column vectors if size(units, 2) > 1 units = units'; end end ================================================ FILE: wrappers/matlab/detectSift3D.m ================================================ function keys = detectSift3D(im, varargin) %detectSift3D(im, options) Detect Sift3D keypoints in an image. % Arguments: % im - An [MxNxP] array, where voxels are indexed in (x, y, z) order. % % Options: % units - See imRead3D. If units are specified, the detected % keypoints are isotropic even when im is not. % peakThresh - The smallest allowed absolute DoG value, as a fraction % of the largest. Must be in the interval (0, 1]. (default: 0.10) % cornerThresh - The smalled allowed corner score, on the interval % [0, 1]. (default: 0.5) % numKpLevels - The number of pyramid levels per octave in which % keypoints are found. Must be a positive integer. (default: 3) % sigmaN - The nominal scale parameter of the input data, on the % interval (0, inf). (default: 1.15) % sigma0 - The scale parameter of the first level of octave 0, in the % interval (0, inf). (default: 1.6) % % Return values: % keys - A [Qx1] array of keypoint structs. See keypoint3D.m for the % struct definition. % % Note: This function will reserve memory that persists after it has % finished. To release all SIFT3D memory, use 'clear mex'. % % Keypoint coordinates are defined in the space of their pyramid level. % To convert them to Matlab indices in the input image, use the following % transformation: % idx = key.coords * pow2(-key.octave) + 1 % % Examples: % im = rand(50, 50, 50); % keys = detectSift3D(im); % % [im, units] = imRead3D('someFile.dcm'); % keys = detectSift3D(im, 'units', units); % % See also: % extractSift3D, imRead3D, imWrite3D, keypoint3D, setupSift3D % % Copyright (c) 2015-2018 Blaine Rister et al., see LICENSE for details. % Option names unitsStr = 'units'; % Parse options parser = Sift3DParser; parser.addParamValue(unitsStr, []) optStruct = parser.parseAndVerify(varargin{:}); units = parser.Results.units; % Verify inputs narginchk(1, inf) if isempty(im) error('im is empty') end if ndims(im) ~= 3 error(['im must have 3 dimensions, detected ' num2str(ndims(im))]); end units = checkUnits3D(units); % Scale and convert the image to single precision im = single(im); im = im / (max(im(:)) + eps); % Detect features keys = mexDetectSift3D(im, units, optStruct); end ================================================ FILE: wrappers/matlab/extractSift3D.m ================================================ function [desc, coords] = extractSift3D(keys, im, units) %extractSift3D(keys, im, units) Extract Sift3D descriptors from keypoints. % Arguments: % keys - An array of n keypoint structs. See keypoint3D.m for the % format. % im - (Optional) An [MxNxP] array, where voxels are indexed in % (x, y, z) order. If im is empty or not provided, this function uses % the Gaussian scale-space pyramid from the most recent call to % detectSift3D. (Note: this data is overwritten by calls to % registerSift3D.) % units - (Optional) See imRead3D. If units are specified, the extracted % descriptors are isotropic even when im is not. % % Return values: % desc - An [n x 768] array of descriptors. The ith row is a descriptor % corresponding to keys(i). % coords - An [n x 3] array of descriptor coordinates, defined in the % space of the input image (see the description of the "im" argument). % Note these coordinates are 0-indexed, so you need to add 1 to get the % equivalent Matlab index. (See detectSift3D.m for more explanation.) % % Examples: % % Extract without units % im = rand(50, 50, 50); % keys = detectSift3D(im); % [desc, coords] = extractSift3D(keys); % % % Extract with units % [im, units] = imRead3D('someFile.dcm'); % keys = detectSift3D(im, units); % [desc, coords] = extractSift3D(keys); % % % Extract from manually-defined keypoints % [im, units] = imRead3D('someFile.dcm'); % keys = keypoint3D([0 0 0]); % keys = orientation3D(keys, im, units); % [desc, coords] = extractSift3D(keys, im, units); % % See also: % detectSift3D, imRead3D, keypoint3D, orientation3D, setupSift3D % % Copyright (c) 2015-2018 Blaine Rister et al., see LICENSE for details. % Required field dimensions coordsSizeReq = [1 3]; oriSizeReq = [3 3]; % The number of descriptor elements descNumel = 768; % Default parameters if nargin < 2 im = []; end if nargin < 3 units = []; end % Verify inputs if nargin < 1 error('Not enough arguments') end if ~isa(keys, 'struct') error('keys must be a struct array') end % Do nothing if we have no keypoints if isempty(keys) warning('keys is empty') desc = []; coords = []; return end coordsSize = size(keys(1).coords); if any(coordsSize ~= coordsSizeReq) error(['keys.coords must have size [' num2str(coordsSizeReq) '].' ... 'Detected size: [' num2str(coordsSize) ']']); end if ~isa(keys(1).coords, 'double') error('keys.coords must have type double'); end if ~isscalar(keys(1).scale) error('keys.scale must be a scalar'); end if ~isa(keys(1).scale, 'double') error('keys.scale must have type double'); end oriSize = size(keys(1).ori); if any(oriSize ~= oriSizeReq) error(['keys.ori must have size [' num2str(oriSizeReq) '].' ... 'Detected size: [' num2str(oriSize) ']']); end if ~isa(keys(1).ori, 'double') error('keys.ori must have type double'); end if nargin < 2 im = []; end % Convert to single precision im = single(im); % Verify and scale the input image, if any if (~isempty(im)) if (ndims(im) ~= 3) error(['im must have 3 dimensions, detected ' num2str(ndims(im))]); end im = im / (max(im(:)) + eps); end units = checkUnits3D(units); % Detect features ret = mexExtractSift3D(keys, im, units); % Splice the outputs coords = ret(:, 1 : 3); desc = ret(:, 4 : end); assert(size(desc, 2) == descNumel); end ================================================ FILE: wrappers/matlab/imRead3D.m ================================================ function [im, units] = imRead3D(path) %imRead3D(im) Read a 3D image from a file. % Arguments: % path - The path to the file. % % Supported file formats: % NIFTI (.nii, .nii.gz) % Analyze (.img, .img.gz) % DICOM or DICOM Segmentation Object (.dcm) % Directory of DICOM files (no extension) % % Return values: % im - An [MxNxPxC] array containing the image data. The last dimension % denotes the channels of the [MxNxP] image. The voxels are indexed % in (x, y, z, c) order, where c is the channel index and (x, y, z) % are the spatial coordinates. NOTE: This is different from the order in % which Matlab usually stores 3D images, where the x-axis is the second % dimension. You must transpose each XY plane (axial slice) before % viewing or manipulating it with Matlab's image processing functions. % units - A [3x1] vector of the (x, y, z) real-world units for the % voxels in im. % % Examples: % [im, units] = imRead3D('image.nii.gz'); % NIFTI % [im, units] = imRead3D('image.dcm'); % Multi-slice DICOM or DSO % [im, units] = imRead3D('image'); % Directory of DICOM slices % % Notes: % When possible, the image values will be in a vendor-neutral format. For % example, a DICOM CT scan will be converted to Hounsfield units. % % When reading a DICOM Segmentation Object (DSO), the program assumes % that the DICOM images to which the DSO refers are located in the same % directory as the DSO. % % See also: % imWrite3D, detectSift3D, extractSift3D, setupSift3D % % Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. % Verify inputs if nargin < 1 || isempty(path) error('Not enough arguments') end if ~exist(path, 'file') error('File does not exist') end % Read the image [im, units] = mexImRead3D(path); end ================================================ FILE: wrappers/matlab/imWrite3D.m ================================================ function imWrite3D(path, im, units) %imWrite3D(im) Write a 3D image to a file. % Arguments: % path - The path to the file. % im - An [MxNxPxC] array containing the image data. See imRead3D. % units - (Optional) See imRead3D. Missing values default to 1. % % Supported file formats: % NIFTI (.nii, .nii.gz) % Analyze (.img, .img.gz) % DICOM (.dcm) % Directory of DICOM files (no extension) % % Examples: % imWrite3D('image.nii.gz', im); % NIFTI % imWrite3D('image.dcm', im); % Multi-slice DICOM % imWrite3D('image', im); % Directory of DICOM slices % imWrite3D('image.dcm', im, [1 1 2]) % Anisotropic, multi-slice DICOM % % Notes: % When writing a DICOM file, the images values will be scaled and % rounded to 8 unsigned bits. % % See also: % imRead3D, detectSift3D, extractSift3D, setupSift3D % % Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. % Default parameters if nargin < 3 || isempty(units) units = ones(3, 1); end % Verify inputs if nargin < 1 || isempty(path) error('path not specified') end if ~isa(path, 'char') error('path must be a string') end if nargin < 2 || isempty(im) error('im not specified') end if ndims(im) > 4 error(['im has invalid dimensionality: ' num2str(ndims(im))]) end units = checkUnits3D(units); % Convert the image to single-precision im = single(im); % Check if this is a .gz file being written on Windows winGz = false; [pathstr, name, ext] = fileparts(path); if strcmp(ext, '.gz') && ispc % Strip .gz from the path winGz = true; path = fullfile(pathstr, name); end % Write the image mexImWrite3D(path, im, units); % On Windows, compress the file from within Matlab. Otherwise, it will % crash. if winGz gzip(path) delete(path) end end ================================================ FILE: wrappers/matlab/keypoint3D.m ================================================ function keys = keypoint(coords, scale, ori) %keypoint3D(coords, scales, orientations) Create an array of Keypoint % structs. % % Inputs: % coords - [Mx3] matrix of locations, where each row is an [x, y, z] % coordinate triple, 0-indexed (required) % scale - [Mx1] vector of nonnegative scale parameters (default: 1.6) % ori - [3x3xM] array, where ori(:, :, i) is the [3x3] rotation matrix % of the ith keypoint (default: identity matrix) % % Leaving an argument empty results in setting that value to the default. % % Return values: % keys - an [Mx1] array of keypoint structs. Each struct has the following % fields: % key.coords - The [x y z] coordinates, 0-indexed. % key.scale - The scale coordinate. % key.ori - A [3x3] rotation matrix representing the 3D orientation. % key.octave - The pyramid octave index. % key.level - The pyramid level index within that octave. % % Example: % coords = [1 1 1; 2 2 2]; % scale = [1 2]; % ori = repmat(eye(3), [1 1 2]); % keys = keypoint3D(coords, scale, ori); % % See also: % orientation3D, extractSift3D, setupSift3D % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Default parameters n = 3; scaleDefault = 1.6; oriDefault = eye(n); % Error tolerance tol = 1E3 * eps; % Verify coords if nargin < 1 || isempty(coords) error('Coords argument must be specified') elseif ~isa(coords, 'double') error('Coords argument must be of type double') elseif ~isreal(coords) error('Coords argument must be real-valued') end if size(coords, 2) ~= n || ~ismatrix(coords) error(['coords argument has invalid dimensions [' ... num2str(size(coords)) '] must be [mx3]']) end m = size(coords, 1); % Verify scale if nargin < 2 || isempty(scale) scale = repmat(scaleDefault, [m 1]); elseif isequal(size(scale), [1 m]) scale = scale'; elseif ~isequal(size(scale), [m 1]) error(['scale argument has invalid dimensions [' ... num2str(size(scale)) '] must be [mx1]']); elseif ~isa(scale, 'double') error('scale argument must be of type double') elseif ~isreal(scale) error('scale argument must be real-valued') elseif any(scale <= 0) error('scale argument must be positive') end % Verify orientation if nargin < 3 || isempty(ori) ori = repmat(oriDefault, [1 1 m]); elseif ndims(ori) == 2 && ~isequal(size(ori), [n n]) || ... ndims(ori) == 3 && ~isequal(size(ori), [n n m]) nStr = num2str(n); error(['ori argument has invalid dimensions [' num2str(size(ori)) ... '], must be [' nStr 'x' nStr 'xm]']) elseif ~isa(ori, 'double') error('scale argument must be of type double') elseif ~isreal(scale) error('scale argument must be real-valued') else % Verify rotation matrices for i = 1 : m R = ori(:, :, i); % Verify determinant d = det(R); if (abs(d - 1) > tol) error(['det(ori(:, :, ' num2str(i) ')) = ' num2str(d) ... ', must be equal or near to 1']) end % Verify orthogonality RRt = R * R'; if (abs(RRt - eye(size(R))) > tol) error(['ori(:, :, ' num2str(i) ')) must be orthogonal']) end end end % Create default octave and level arrays octave = 0; level = 0; % Create the struct keys = struct('coords', num2cell(coords, 2), 'scale', ... num2cell(scale, 2), 'ori', reshape(num2cell(ori, [1 2]), [m 1]), ... 'octave', num2cell(octave, 2), 'level', num2cell(level, 2)); end ================================================ FILE: wrappers/matlab/matchSift3D.m ================================================ function matches = matchSift3D(desc1, coords1, desc2, coords2, nnThresh) %matchSift3D(desc1, desc2) % Match SIFT3D desriptors from a pair of images. % % Arguments: % desc1: Descriptors returned from extractSift3D, for the first image. % coords1: Coordinates returned from extractSift3D, for the second % image. (Default: zeros) % desc2: Like desc1, but for the second image. % coords2: Like coords1, but for the second image. (Default: zeros) % nnThresh: The matching threshold, in the interval (0, 1]. A higher % value means fewer matches will be returned. (Default: 0.8) % % Return values: % matches - An [m x 2] array, where matches(m, 1) indexes a row of % desc1, and matches(m, 2) indexes a row of desc2. The descriptors % given by these % % If either of coords1, coords2 are empty, default values will be used. % % Example: % % Get descriptors from the first image % [im1, units1] = imRead3D('someFile.dcm'); % keys1 = detectSift3D(im1, units1); % [desc1, coords1] = extractSift3D(keys1); % % % Get descriptors from the second image % [im2, units2] = imRead3D('someOtherFile.dcm'); % keys2 = detectSift3D(im2, units2); % [desc2, coords2] = extractSift3D(keys2); % % % Match the descriptors % matches = matchSift3D(desc1, coords1, desc2, coords2, 0.8); % % See also: % imRead3D, imWrite3D, registerSift3D, setupSift3D % Supply defaults if isempty(coords1) coords1 = zeros(size(desc1, 1), 3); end if nargin < 4 || isempty(coords2) coords2 = zeros(size(desc2, 1), 3); end if nargin < 5 nnThresh = []; end % Verify inputs narginchk(3, 4) validateDesc('desc1', desc1, 'coords1', coords1) validateDesc('desc2', desc2, 'coords2', coords2) if ~isempty(nnThresh) validateattributes(nnThresh, {'numeric'}, ... {'real', 'scalar', '>=', 0, '<', 1}, 'nnThresh') end % Format the descriptors as a C-style matrix desc1f = formatDesc(desc1, coords1); desc2f = formatDesc(desc2, coords2); % Match the descriptors matchIdx = mexMatchSift3D(desc1f, desc2f, nnThresh); % Convert the matches to indices in each array validMatches = matchIdx >= 0; matches = [find(validMatches), matchIdx(validMatches) + 1]; end function validateDesc(descName, desc, coordsName, coords) % Verify the descriptors and coordinates, throwing errors if invalid. validateattributes(desc, {'numeric'}, {'real', '2d', 'ncols', 768}, ... descName) validateattributes(coords, {'numeric'}, {'real', '2d', 'ncols', 3}, ... coordsName) if size(desc, 1) ~= size(coords, 1) error([descName ' has size ' size(desc) ', but ' coordsName ' has ' ... 'size ' size(coords) '. Number of rows must be equal.']) end end function ret = formatDesc(desc, coords) % Combine the coordinates and descriptors and convert to double ret = double([coords, desc]); end ================================================ FILE: wrappers/matlab/mexDetectSift3D.c ================================================ /* ----------------------------------------------------------------------------- * mexDetectSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to detect SIFT3D keypoints. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "sift.h" #include "immacros.h" #include "mexutil.h" #include "mex.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { const mxArray *mxIm, *mxUnits, *mxOpts; Image im; Keypoint_store kp; const char *errName, *errMsg; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&im); \ cleanup_Keypoint_store(&kp); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 3) err_msg("main:numInputs", "This function takes 3 inputs."); // Verify the number of outputs if (nlhs > 1) err_msg("main:numOutputs", "This function takes 1 output."); // Assign the inputs mxIm = prhs[0]; mxUnits = prhs[1]; mxOpts = prhs[2]; // Set the options if (mex_set_opts_SIFT3D(mxOpts)) CLEAN_AND_QUIT("main:setOpts", "Failed to set the options.", SIFT3D_FALSE); // Initialize intermediates init_Keypoint_store(&kp); init_im(&im); // Copy the transposed image if (mx2imWithUnits(mxIm, mxUnits, &im)) CLEAN_AND_QUIT("main:copyIm", "Failed to convert the input " "image", SIFT3D_TRUE); // Detect keypoints if (mex_SIFT3D_detect_keypoints(&im, &kp)) CLEAN_AND_QUIT("main:detect", "Failed to detect keypoints", SIFT3D_TRUE); // Convert the output to a MATLAB array of structs if ((plhs[0] = kp2mx(&kp)) == NULL) CLEAN_AND_QUIT("main:convertToStructs", "Failed to convert " "keypoints to structs", SIFT3D_FALSE); // Clean up im_free(&im); cleanup_Keypoint_store(&kp); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexExtractSift3D.c ================================================ /* ----------------------------------------------------------------------------- * mexExtractSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to extract SIFT3D descriptors. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "sift.h" #include "mexutil.h" #include "mex.h" /* Entry point. * Output format: * [x y z el0 el1 ... el767] (see sift.c:SIFT3D_Descriptor_store_to_Mat_rm) * * Note that the matlab function does some postprocessing to present the data * in a different output format. */ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { const mxArray *mxKp, *mxIm, *mxUnits; const char *errMsg; Image im; Keypoint_store kp; SIFT3D_Descriptor_store desc; int i; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&im); \ cleanup_Keypoint_store(&kp); \ cleanup_SIFT3D_Descriptor_store(&desc); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 3) err_msgu("main:numInputs", "This function takes 3 inputs."); // Verify the number of outputs if (nlhs > 1) err_msgu("main:numOutputs", "This function takes 1 output."); // Assign inputs mxKp = prhs[0]; mxIm = prhs[1]; mxUnits = prhs[2]; // Initialize intermediates init_im(&im); init_Keypoint_store(&kp); init_SIFT3D_Descriptor_store(&desc); // Convert the keypoints if (mx2kp(mxKp, &kp)) CLEAN_AND_QUIT("main:convertKp", "Failed to convert keypoints", SIFT3D_TRUE); // Process the image and extract descriptors if (!mxIsEmpty(mxIm)) { // Convert the input to an Image struct if (mx2imWithUnits(mxIm, mxUnits, &im)) CLEAN_AND_QUIT("main:convertIm", "Failed to convert image", SIFT3D_TRUE); // Extract raw descriptors if (mex_SIFT3D_extract_raw_descriptors(&im, &kp, &desc)) CLEAN_AND_QUIT("main:extractRaw", "Failed to extract raw descriptors", SIFT3D_TRUE); } else { // Attempt to retrieve the Gaussian pyramid if (!mexHaveGpyr) CLEAN_AND_QUIT("main:haveGpyr", "Failed to get the Gaussian pyramid. Must " "call detectSift3D before this function can " "be called without the im argument.", SIFT3D_TRUE); // Extract descriptors from the pyramid if (mex_SIFT3D_extract_descriptors(&kp, &desc)) CLEAN_AND_QUIT("main:extractPyramid", "Failed to extract pyramid descriptors", SIFT3D_FALSE); } // Convert the descriptors to an output matrix if ((plhs[0] = desc2mx(&desc)) == NULL) CLEAN_AND_QUIT("main:createOutput", "Failed to convert descriptors", SIFT3D_FALSE); // Clean up im_free(&im); cleanup_Keypoint_store(&kp); cleanup_SIFT3D_Descriptor_store(&desc); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexImRead3D.c ================================================ /* ----------------------------------------------------------------------------- * mexImRead3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to read 3D images. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "mexutil.h" #include "mex.h" #include "matrix.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { Image im; const mxArray *mxPath, *mxIm; const char *path; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&im); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 1) err_msgu("main:numInputs", "This function takes 1 input."); // Verify the number of outputs if (nlhs > 2) err_msgu("main:numOutputs", "This function takes 2 outputs."); // Assign the inputs mxPath = prhs[0]; // Initialize intermediates init_im(&im); // Get the path string if ((path = mxArrayToString(mxPath)) == NULL) CLEAN_AND_QUIT("main:getPath", "Failed to convert the input " "to a string", SIFT3D_FALSE); // Load the image switch (im_read(path, &im)) { case SIFT3D_SUCCESS: break; case SIFT3D_FILE_DOES_NOT_EXIST: CLEAN_AND_QUIT("main:dne", "File does not exist", SIFT3D_TRUE); case SIFT3D_UNSUPPORTED_FILE_TYPE: CLEAN_AND_QUIT("main:unsupportedType", "Unsupported file " "type", SIFT3D_TRUE); case SIFT3D_WRAPPER_NOT_COMPILED: CLEAN_AND_QUIT("main:notCompiled", "Recompile SIFT3D " "with support for this file type", SIFT3D_TRUE); case SIFT3D_UNEVEN_SPACING: CLEAN_AND_QUIT("main:unevenSpacing", "The series has " "uneven slice spacing", SIFT3D_TRUE); case SIFT3D_INCONSISTENT_AXES: CLEAN_AND_QUIT("main:inconsistentAxes", "The series " "has inconsistent slice axes", SIFT3D_TRUE); case SIFT3D_DUPLICATE_SLICES: CLEAN_AND_QUIT("main:duplicateSlices", "The series " "contains slices in duplicate locations", SIFT3D_TRUE); default: CLEAN_AND_QUIT("main:unexpected", "Unexpected error " "reading the image", SIFT3D_TRUE); } // Convert the output image to a MATLAB array if ((plhs[0] = im2mx(&im)) == NULL) CLEAN_AND_QUIT("main:im2mx", "Failed to convert image to an " "mxArray", SIFT3D_FALSE); // Convert the output units to a MATLAB array if ((plhs[1] = units2mx(&im)) == NULL) CLEAN_AND_QUIT("main:im2mx", "Failed to convert units to an " "mxArray", SIFT3D_FALSE); // Clean up im_free(&im); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexImWrite3D.c ================================================ /* ----------------------------------------------------------------------------- * mexImWrite3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to write 3D images. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "mexutil.h" #include "mex.h" #include "matrix.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { Image im; const mxArray *mxPath, *mxIm, *mxUnits; const char *path; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&im); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 3) err_msgu("main:numInputs", "This function takes 3 inputs."); // Verify the number of outputs if (nlhs != 0) err_msgu("main:numOutputs", "This function takes no outputs."); // Assign the inputs mxPath = prhs[0]; mxIm = prhs[1]; mxUnits = prhs[2]; // Initialize intermediates init_im(&im); // Get the path string if ((path = mxArrayToString(mxPath)) == NULL) CLEAN_AND_QUIT("main:getPath", "Failed to convert the input " "to a string", SIFT3D_FALSE); // Convert the image to the internal format if (mx2imWithUnits(mxIm, mxUnits, &im)) CLEAN_AND_QUIT("main:mx2im", "Failed to convert the input " "image to the internal format", SIFT3D_TRUE); // Write the image switch (im_write(path, &im)) { case SIFT3D_SUCCESS: break; case SIFT3D_UNSUPPORTED_FILE_TYPE: CLEAN_AND_QUIT("main:unsupportedType", "Unsupported file " "type", SIFT3D_TRUE); case SIFT3D_WRAPPER_NOT_COMPILED: CLEAN_AND_QUIT("main:notCompiled", "Recompile SIFT3D " "with support for this file type", SIFT3D_TRUE); default: CLEAN_AND_QUIT("main:unexpected", "Unexpected error " "writing the image", SIFT3D_TRUE); } // Clean up im_free(&im); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexMatchSift3D.c ================================================ /* ----------------------------------------------------------------------------- * mexMatchSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2017 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to register images from SIFT3D keypoints and descriptors. * ----------------------------------------------------------------------------- */ #include #include "imutil.h" #include "immacros.h" #include "sift.h" #include "reg.h" #include "mexutil.h" #include "mex.h" #include "matrix.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { const mxArray *mxDesc1, *mxDesc2, *mxNnThresh; const mwSize *mxDims; mxArray *mxMatches; double *mxMatchesData; int *matches; SIFT3D_Descriptor_store desc1, desc2; double nn_thresh; int i; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ cleanup_SIFT3D_Descriptor_store(&desc1); \ cleanup_SIFT3D_Descriptor_store(&desc2); \ if (matches != NULL) { \ free(matches); \ } \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 3) err_msg("main:numInputs", "This function takes 3 inputs."); // Verify the number of outputs if (nlhs > 1) err_msg("main:numOutputs", "This function takes 1 output."); // Assign the inputs mxDesc1 = prhs[0]; mxDesc2 = prhs[1]; mxNnThresh = prhs[2]; // Get the matching threshold, if one was provided nn_thresh = mxIsEmpty(mxNnThresh) ? SIFT3D_nn_thresh_default : mxGetScalar(mxNnThresh); // Initialize intermediates matches = NULL; init_SIFT3D_Descriptor_store(&desc1); init_SIFT3D_Descriptor_store(&desc2); // Convert the inputs to descriptor stores if (mx2desc(mxDesc1, &desc1)) CLEAN_AND_QUIT("main:convert1", "Failed to convert desc1", SIFT3D_TRUE); if (mx2desc(mxDesc2, &desc2)) CLEAN_AND_QUIT("main:convert2", "Failed to convert desc2", SIFT3D_TRUE); // Match descriptors if (SIFT3D_nn_match(&desc1, &desc2, nn_thresh, &matches)) CLEAN_AND_QUIT("main:match", "Failed to match descriptors", SIFT3D_FALSE); // Create a matrix for the output if ((mxMatches = mxCreateDoubleMatrix(desc1.num, 1, mxREAL)) == NULL) CLEAN_AND_QUIT("main:createOutput", "Failed to create output", SIFT3D_TRUE); plhs[0] = mxMatches; // Get the data if ((mxMatchesData = mxGetData(mxMatches)) == NULL) CLEAN_AND_QUIT("main:convertOut", "Failed to get output data", SIFT3D_FALSE); // Copy the output for (i = 0; i < desc1.num; i++) { mxMatchesData[i] = (double) matches[i]; } // Clean up cleanup_SIFT3D_Descriptor_store(&desc1); cleanup_SIFT3D_Descriptor_store(&desc2); free(matches); } ================================================ FILE: wrappers/matlab/mexOrientation3D.c ================================================ /* ----------------------------------------------------------------------------- * mexOrientation3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to assign 3D orientation. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "sift.h" #include "immacros.h" #include "mexutil.h" #include "mex.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { const mxArray *mxKp, *mxIm, *mxUnits; Image im; Keypoint_store kp; const char *errName, *errMsg; double *conf; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&im); \ cleanup_Keypoint_store(&kp); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 3) err_msg("main:numInputs", "This function takes 3 inputs."); // Verify the number of outputs if (nlhs > 2) err_msg("main:numOutputs", "This function takes 2 outputs."); // Assign the inputs mxKp = prhs[0]; mxIm = prhs[1]; mxUnits = prhs[2]; // Initialize intermediates init_im(&im); init_Keypoint_store(&kp); // Convert the keypoints if (mx2kp(mxKp, &kp)) CLEAN_AND_QUIT("main:convertKey", "failed to convert the " "input keypoints", SIFT3D_TRUE); // Convert the image if (mx2imWithUnits(mxIm, mxUnits, &im)) CLEAN_AND_QUIT("main:copyIm", "Failed to convert the input " "image", SIFT3D_TRUE); // Assign the orientations conf = NULL; if (mex_SIFT3D_assign_orientations(&im, &kp, &conf)) CLEAN_AND_QUIT("main:assignOrientations", "Failed to assign " "the orientations", SIFT3D_TRUE); // Convert the output keypoints if ((plhs[0] = kp2mx(&kp)) == NULL) CLEAN_AND_QUIT("main:convertR", "Failed to convert R", SIFT3D_FALSE); // Convert the output confidence to a MATLAB array if ((plhs[1] = array2mx(conf, kp.slab.num)) == NULL) CLEAN_AND_QUIT("main:convertConf", "Failed to convert conf", SIFT3D_FALSE); // Clean up im_free(&im); cleanup_Keypoint_store(&kp); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexRegisterSift3D.c ================================================ /* ----------------------------------------------------------------------------- * mexRegisterSift3D.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex interface to register images from SIFT3D keypoints and descriptors. * ----------------------------------------------------------------------------- */ #include "imutil.h" #include "reg.h" #include "immacros.h" #include "mexutil.h" #include "mex.h" #include "matrix.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { const mxArray *mxSrc, *mxRef, *mxSrcUnits, *mxRefUnits, *mxResample, *mxRegOpts, *mxSIFT3DOpts; Image src, ref; Affine aff; Mat_rm match_src, match_ref; int ret; /* Clean up and print an error */ #define CLEAN_AND_QUIT(name, msg, expected) { \ im_free(&src); \ im_free(&ref); \ cleanup_tform(&aff); \ cleanup_Mat_rm(&match_src); \ cleanup_Mat_rm(&match_ref); \ if (expected) { \ err_msg(name, msg); \ } else { \ err_msgu(name, msg); \ } \ } // Verify the number of inputs if (nrhs != 7) err_msg("main:numInputs", "This function takes 7 inputs."); // Verify the number of outputs if (nlhs > 3) err_msg("main:numOutputs", "This function takes 3 outputs."); // Assign the inputs mxSrc = prhs[0]; mxRef = prhs[1]; mxSrcUnits = prhs[2]; mxRefUnits = prhs[3]; mxResample = prhs[4]; mxRegOpts = prhs[5]; mxSIFT3DOpts = prhs[6]; // Verify the resampling option if (!mxIsLogicalScalar(mxResample)) err_msg("main:resample", "Argument 'resample' must be a " "logical scalar."); // Initialize intermediates if (init_Affine(&aff, IM_NDIMS) || init_Mat_rm(&match_src, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE) || init_Mat_rm(&match_ref, 0, 0, SIFT3D_DOUBLE, SIFT3D_FALSE)) err_msgu("main:init", "Failed to initialize intermediates"); init_im(&src); init_im(&ref); // Convert the inputs to images if (mx2imWithUnits(mxSrc, mxSrcUnits, &src)) CLEAN_AND_QUIT("main:convertSrc", "Failed to convert the " "source image.", SIFT3D_TRUE); if (mx2imWithUnits(mxRef, mxRefUnits, &ref)) CLEAN_AND_QUIT("main:convertRef", "Failed to convert the " "reference image.", SIFT3D_TRUE); // Set the options if (mex_set_opts_Reg_SIFT3D(mxRegOpts)) CLEAN_AND_QUIT("main:setOpts", "Failed to set the registration " "options.", SIFT3D_FALSE); if (mex_set_opts_SIFT3D(mxSIFT3DOpts)) CLEAN_AND_QUIT("main:setOpts", "Failed to set the SIFT3D " "options.", SIFT3D_FALSE); // Register the images if (mxIsLogicalScalarTrue(mxResample)) { ret = mex_register_SIFT3D_resample(&src, &ref, LINEAR, &aff); } else { ret = mex_set_src_Reg_SIFT3D(&src) || mex_set_ref_Reg_SIFT3D(&ref) || mex_register_SIFT3D(&aff) ? SIFT3D_FAILURE : SIFT3D_SUCCESS; } // Handle registration errors if (ret) { CLEAN_AND_QUIT("main:reg", "Failed to register the images. " "Possibly no good transformation was found.", SIFT3D_TRUE); } // Retrieve the matches if (mex_get_matches_Reg_SIFT3D(&match_src, &match_ref)) CLEAN_AND_QUIT("main:getMatches", "Failed to retrieve the " "matches.", SIFT3D_FALSE); // Convert the outputs to mxArrays if ((plhs[0] = mat2mx(&aff.A)) == NULL || (plhs[1] = mat2mx(&match_src)) == NULL || (plhs[2] = mat2mx(&match_ref)) == NULL) CLEAN_AND_QUIT("main:convertA", "Failed to convert " "outputs.", SIFT3D_FALSE); // Clean up im_free(&src); im_free(&ref); cleanup_tform(&aff); cleanup_Mat_rm(&match_src); cleanup_Mat_rm(&match_ref); #undef CLEAN_AND_QUIT } ================================================ FILE: wrappers/matlab/mexutil.c ================================================ /* ----------------------------------------------------------------------------- * mexutil.c * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Mex utility library for SIFT3D. * ----------------------------------------------------------------------------- */ /* Standard headers */ #include #include /* SIFT3D headers */ #include "imtypes.h" #include "immacros.h" #include "imutil.h" #include "sift.h" #include "reg.h" #include "mexutil.h" /* Matlab headers */ #include #include "mex.h" #include "matrix.h" /* The number of dimensions in mxArrays representing images */ #define MX_IM_NDIMS (IM_NDIMS + 1) /* Keypoint struct information */ #define COORDS_NAME "coords" #define SCALE_NAME "scale" #define ORI_NAME "ori" #define OCTAVE_NAME "octave" #define LEVEL_NAME "level" const char *fieldNames[] = { COORDS_NAME, SCALE_NAME, ORI_NAME, OCTAVE_NAME, LEVEL_NAME }; const mwSize kpNDims = 1; const int kpNFields = sizeof(fieldNames) / sizeof(char *); /* Global state */ Reg_SIFT3D reg; /* Error message tag */ const char *tag = "sift3D"; /* Static helper functions */ static void init(void) __attribute__((constructor)); static void fini(void) __attribute__((destructor)); /* Library initialization */ static void init(void) { if (init_Reg_SIFT3D(®)) err_msgu("main:initSift", "Failed to initialize SIFT3D"); } /* Library cleanup */ static void fini(void) { cleanup_Reg_SIFT3D(®); } /* Print an error message. */ void err_msg(const char *name, const char *msg) { char id[1024]; sprintf(id, "%s:%s", tag, name); mexErrMsgIdAndTxt(id, msg); } /* Print an unexpected error. */ void err_msgu(const char *name, const char *msg) { err_msg(name, msg); print_bug_msg(); } /* Returns SIFT3D_TRUE if the input is a real-valued double-precision floating * point array, aka "double" in C. */ int isDouble(const mxArray *const mx) { return mxIsDouble(mx) && !mxIsComplex(mx); } /* Returns the index in an mxArray of the voxel at the coordinates (x, y, z) * and channel c */ mwIndex mxImGetIdx(const mxArray *const mx, const int x, const int y, const int z, const int c) { const mwIndex subs[] = {x, y, z, c}; const mwSize nSubs = sizeof(subs) / sizeof(mwIndex); assert(nSubs == MX_IM_NDIMS); return mxCalcSingleSubscript(mx, nSubs, subs); } /* Convert an Image to an mxArray. The output will have IM_NDIMS + 1 dimensions * and type double. The final dimension denotes the image channels. * * Returns a pointer to the array, or NULL if an error has occurred. */ mxArray *im2mx(const Image *const im) { mwSize dims[MX_IM_NDIMS]; mxArray *mx; double *mxData; int i, x, y, z, c; // Initialize the dimensions for (i = 0; i < IM_NDIMS; i++) { dims[i] = SIFT3D_IM_GET_DIMS(im)[i]; } dims[IM_NDIMS] = im->nc; // Create an array if ((mx = mxCreateNumericArray(MX_IM_NDIMS, dims, mxDOUBLE_CLASS, mxREAL)) == NULL) return NULL; // Get the data if ((mxData = mxGetData(mx)) == NULL) return NULL; // Transpose and copy the data SIFT3D_IM_LOOP_START_C(im, x, y, z, c) const mwIndex idx = mxImGetIdx(mx, x, y, z, c); mxData[idx] = (double) SIFT3D_IM_GET_VOX(im, x, y, z, c); SIFT3D_IM_LOOP_END_C return mx; } /* Convert an mxArray to an Image. mx must have at most IM_NDIMS + 1 * dimensions and be of type single (float). */ int mx2im(const mxArray *const mx, Image *const im) { const mwSize *mxDims; float *mxData; mwIndex mxNDims; int i, x, y, z, c, mxNSpaceDims; // Verify inputs mxNDims = (int) mxGetNumberOfDimensions(mx); if (mxNDims > MX_IM_NDIMS || !mxIsSingle(mx) || mxIsComplex(mx)) return SIFT3D_FAILURE; // Copy the spatial dimensions mxNSpaceDims = SIFT3D_MIN((int) mxNDims, MX_IM_NDIMS - 1); mxDims = mxGetDimensions(mx); for (i = 0; i < mxNSpaceDims; i++) { SIFT3D_IM_GET_DIMS(im)[i] = (int) mxDims[i]; } // Pad the unfilled dimensions with 1 for (i = mxNSpaceDims; i < MX_IM_NDIMS - 1; i++) { SIFT3D_IM_GET_DIMS(im)[i] = 1; } // Copy the number of channels, defaulting to 1 im->nc = mxNDims == MX_IM_NDIMS ? (int) mxDims[MX_IM_NDIMS - 1] : 1; // Resize the output im_default_stride(im); if (im_resize(im)) return SIFT3D_FAILURE; // Get the data if ((mxData = mxGetData(mx)) == NULL) return SIFT3D_FAILURE; // Transpose and copy the data SIFT3D_IM_LOOP_START_C(im, x, y, z, c) mwIndex idx; idx = mxImGetIdx(mx, x, y, z, c); SIFT3D_IM_GET_VOX(im, x, y, z, c) = mxData[idx]; SIFT3D_IM_LOOP_END_C return SIFT3D_SUCCESS; } /* Returns an mxArray representing the units of image im. * * Parameters: * Return: the array, or NULL on failure. */ mxArray *units2mx(const Image *const im) { mxArray *mx; double *mxData; int i; // Create an array if ((mx = mxCreateDoubleMatrix(IM_NDIMS, 1, mxREAL)) == NULL) return NULL; // Get the data if ((mxData = mxGetData(mx)) == NULL) goto units2mx_quit; // Copy the data from im for (i = 0; i < IM_NDIMS; i++) { mwIndex idx; const mwIndex subs[] = {i, 0}; const mwIndex nSubs = sizeof(subs) / sizeof(mwIndex); idx = mxCalcSingleSubscript(mx, nSubs, subs); mxData[idx] = SIFT3D_IM_GET_UNITS(im)[i]; } return mx; units2mx_quit: mxDestroyArray(mx); return NULL; } /* Set the units of an image to the data stored in an mxArray, * * Parameters: * -mx: An array of IM_NDIMS dimensions, type double. * -im: The Image struct to which the units are written. * * Return: SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int mx2units(const mxArray *const mx, Image *const im) { const mwSize *mxDims; double *mxData; mwIndex mxNDims; int i; // Verify inputs mxNDims = (int) mxGetNumberOfDimensions(mx); if (mxNDims != 2 || !isDouble(mx)) return SIFT3D_FAILURE; // Verify the dimensions mxDims = mxGetDimensions(mx); if (mxDims[0] != IM_NDIMS || mxDims[1] != 1) return SIFT3D_FAILURE; // Get the data if ((mxData = mxGetData(mx)) == NULL) return SIFT3D_FAILURE; // Copy the data to im for (i = 0; i < IM_NDIMS; i++) { mwIndex idx; const mwIndex subs[] = {i, 0}; const mwIndex nSubs = sizeof(subs) / sizeof(mwIndex); idx = mxCalcSingleSubscript(mx, nSubs, subs); SIFT3D_IM_GET_UNITS(im)[i] = mxData[idx]; } return SIFT3D_SUCCESS; } /* Wrapper around mx2im and mx2units. */ int mx2imWithUnits(const mxArray *const data, const mxArray *const units, Image *const im) { return mx2im(data, im) || mx2units(units, im); } /* Convert a Mat_rm struct to an mxArray. Returns the array, or NULL on * failure. The returned array has type double, regardless of the input array * type. * * Returns the array, or NULL on failure. */ mxArray *mat2mx(const Mat_rm *const mat) { mxArray *mx; double *mxData; int i, j; const int rows = mat->num_rows; const int cols = mat->num_cols; // Create an array if ((mx = mxCreateDoubleMatrix(rows, cols, mxREAL)) == NULL) return NULL; // Get the data if ((mxData = mxGetData(mx)) == NULL) goto mat2mx_quit; #define TRANSPOSE_AND_COPY(type) \ SIFT3D_MAT_RM_LOOP_START(mat, i, j) \ \ mwIndex idx; \ \ const mwIndex subs[] = {i, j}; \ \ idx = mxCalcSingleSubscript(mx, 2, subs); \ mxData[idx] = (double) SIFT3D_MAT_RM_GET(mat, i, j, type); \ \ SIFT3D_MAT_RM_LOOP_END // Transpose and copy the data SIFT3D_MAT_RM_TYPE_MACRO(mat, mat2mx_quit, TRANSPOSE_AND_COPY); #undef TRANSPOSE_AND_COPY return mx; mat2mx_quit: mxDestroyArray(mx); return NULL; } /* Convert an mxArray to a Mat_rm. mx must have type double. The type of mat * is preserved. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int mx2mat(const mxArray *const mx, Mat_rm *const mat) { const mwSize *mxDims; double *data; int i, j; // Verify inputs if (!isDouble(mx)) { SIFT3D_ERR("mx2mat: mx must have type double"); return SIFT3D_FAILURE; } if (mxGetNumberOfDimensions(mx) != 2) { SIFT3D_ERR("mx2mat: mx must be a matrix"); return SIFT3D_FAILURE; } // Resize the output mxDims = mxGetDimensions(mx); mat->num_rows = (int) mxDims[0]; mat->num_cols = (int) mxDims[1]; if (resize_Mat_rm(mat)) return SIFT3D_FAILURE; // Get the data if ((data = mxGetData(mx)) == NULL) return SIFT3D_FAILURE; #define COPY_DATA(type) \ SIFT3D_MAT_RM_LOOP_START(mat, i, j) \ \ mwIndex idx; \ \ const mwIndex subs[] = {i, j}; \ \ idx = mxCalcSingleSubscript(mx, 2, subs); \ SIFT3D_MAT_RM_GET(mat, i, j, type) = (type) data[idx]; \ \ SIFT3D_MAT_RM_LOOP_END // Copy the transposed data SIFT3D_MAT_RM_TYPE_MACRO(mat, mx2mat_quit, COPY_DATA); #undef COPY_DATA return SIFT3D_SUCCESS; mx2mat_quit: return SIFT3D_FAILURE; } /* Convert from a Keypoint_store to an array of MATLAB keypoint structs. * Returns the array of keypoints, or NULL on failure. */ mxArray *kp2mx(const Keypoint_store *const kp) { mxArray *mxKp; int i, coordsNum, scaleNum, oriNum, octaveNum, levelNum; const mwSize numKp = (mwSize) kp->slab.num; // Make an array of structs for the output mxKp = mxCreateStructArray(kpNDims, &numKp, kpNFields, fieldNames); if (mxKp == NULL) return NULL; // Get the field indices in the structs if ((coordsNum = mxGetFieldNumber(mxKp, COORDS_NAME)) < 0 || (scaleNum = mxGetFieldNumber(mxKp, SCALE_NAME)) < 0 || (oriNum = mxGetFieldNumber(mxKp, ORI_NAME)) < 0 || (octaveNum = mxGetFieldNumber(mxKp, OCTAVE_NAME)) < 0 || (levelNum = mxGetFieldNumber(mxKp, LEVEL_NAME)) < 0) return NULL; // Write the keypoints to the output for (i = 0; i < kp->slab.num; i++) { mxArray *mxCoords, *mxScale, *mxOri, *mxOctave, *mxLevel; double *coords; const Keypoint *const key = kp->buf + i; // Initialize the coordinate array if ((mxCoords = mxCreateDoubleMatrix(1, IM_NDIMS, mxREAL)) == NULL) return NULL; // Copy the coordinates coords = mxGetData(mxCoords); coords[0] = key->xd; coords[1] = key->yd; coords[2] = key->zd; // Copy the transposed orientation if ((mxOri = mat2mx(&key->R)) == NULL) return NULL; // Copy the scale mxScale = mxCreateDoubleScalar(key->sd); // Copy the octave index mxOctave = mxCreateDoubleScalar((double) key->o); // Copy the level index mxLevel = mxCreateDoubleScalar((double) key->s); // Set the struct fields mxSetFieldByNumber(mxKp, i, coordsNum, mxCoords); mxSetFieldByNumber(mxKp, i, scaleNum, mxScale); mxSetFieldByNumber(mxKp, i, oriNum, mxOri); mxSetFieldByNumber(mxKp, i, octaveNum, mxOctave); mxSetFieldByNumber(mxKp, i, levelNum, mxLevel); } return mxKp; } /* Convert an array of MATLAB keypoint structs to a Keypoint_store. mx must be * a vector, and each struct element must have type double. */ int mx2kp(const mxArray *const mx, Keypoint_store *const kp) { const mwSize *mxDims, *coordsDims, *oriDims; mxArray *mxCoords, *mxScale, *mxOri, *mxOctave, *mxLevel; mwSize numKp, kpRows, kpCols; int i, coordsNum, scaleNum, oriNum, octaveNum, levelNum; // Verify the number of dimensions if (mxGetNumberOfDimensions(mx) != 2) return SIFT3D_FAILURE; // Parse the input dimensions mxDims = mxGetDimensions(mx); kpRows = mxDims[0]; kpCols = mxDims[1]; if (kpRows == 1) numKp = kpCols; else if (kpCols == 1) numKp = kpRows; else return SIFT3D_FAILURE; // Verify the struct size if (!mxIsStruct(mx) || mxGetNumberOfFields(mx) != kpNFields) return SIFT3D_FAILURE; // Get the field indices if ((coordsNum = mxGetFieldNumber(mx, COORDS_NAME)) < 0 || (scaleNum = mxGetFieldNumber(mx, SCALE_NAME)) < 0 || (oriNum = mxGetFieldNumber(mx, ORI_NAME)) < 0 || (octaveNum = mxGetFieldNumber(mx, OCTAVE_NAME)) < 0 || (levelNum = mxGetFieldNumber(mx, LEVEL_NAME)) < 0) return SIFT3D_FAILURE; // Get the fields of the first keypoint if ((mxCoords = mxGetFieldByNumber(mx, 0, coordsNum)) == NULL || (mxScale = mxGetFieldByNumber(mx, 0, scaleNum)) == NULL || (mxOri = mxGetFieldByNumber(mx, 0, oriNum)) == NULL || (mxOctave = mxGetFieldByNumber(mx, 0, octaveNum)) == NULL || (mxLevel = mxGetFieldByNumber(mx, 0, levelNum)) == NULL) return SIFT3D_FAILURE; // Verify the type if (!isDouble(mxCoords) || !isDouble(mxScale) || !isDouble(mxOri) || !isDouble(mxOctave) || !isDouble(mxLevel)) return SIFT3D_FAILURE; // Verify the number of dimensions if (mxGetNumberOfDimensions(mxCoords) != 2 || mxGetNumberOfDimensions(mxScale) != 2 || mxGetNumberOfDimensions(mxOri) != 2 || mxGetNumberOfDimensions(mxOctave) != 2 || mxGetNumberOfDimensions(mxLevel) != 2) return SIFT3D_FAILURE; // Verify the scalars if (!mxIsScalar(mxScale) || !mxIsScalar(mxOctave) || !mxIsScalar(mxLevel)) return SIFT3D_FAILURE; // Verify the coordinate vector dimensions coordsDims = mxGetDimensions(mxCoords); if ((coordsDims[0] != 1 && coordsDims[1] != 1) || coordsDims[0] * coordsDims[1] != IM_NDIMS) return SIFT3D_FAILURE; // Verify the orientation matrix dimensions oriDims = mxGetDimensions(mxOri); if (oriDims[0] != IM_NDIMS || oriDims[1] != IM_NDIMS) return SIFT3D_FAILURE; // Allocate space in the keypoint store if (resize_Keypoint_store(kp, (size_t) numKp)) return SIFT3D_FAILURE; // Copy the data for (i = 0; i < (int) numKp; i++) { double *coordsData; Keypoint *const key = kp->buf + i; const mwIndex idx = (mwIndex) i; // Get the matrices if ((mxCoords = mxGetFieldByNumber(mx, idx, coordsNum)) == NULL || (mxScale = mxGetFieldByNumber(mx, idx, scaleNum)) == NULL || (mxOri = mxGetFieldByNumber(mx, idx, oriNum)) == NULL || (mxOctave = mxGetFieldByNumber(mx, idx, octaveNum)) == NULL || (mxLevel = mxGetFieldByNumber(mx, idx, levelNum)) == NULL) return SIFT3D_FAILURE; // Copy the scalars key->sd = mxGetScalar(mxScale); key->o = (int) mxGetScalar(mxOctave); key->s = (int) mxGetScalar(mxLevel); // Get the coordinate data if ((coordsData = mxGetData(mxCoords)) == NULL) return SIFT3D_FAILURE; // Copy the coordinate vector key->xd = coordsData[0]; key->yd = coordsData[1]; key->zd = coordsData[2]; // Initialize the orientation matrix if (init_Mat_rm_p(&key->R, key->r_data, IM_NDIMS, IM_NDIMS, SIFT3D_FLOAT, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Copy the orientation matrix if (mx2mat(mxOri, &key->R)) return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; } /* Converts a SIFT3D_Descriptor_store to an mxArray. * * Output format: * [x y z el0 el1 ... el767] (see sift.c:SIFT3D_Descriptor_store_to_Mat_rm) * * Returns a pointer to the array, or NULL on failure. */ mxArray *desc2mx(const SIFT3D_Descriptor_store *const desc) { mxArray *mx; Mat_rm mat; // Initialize intermediates if (init_Mat_rm(&mat, 0, 0, SIFT3D_FLOAT, SIFT3D_FALSE)) return NULL; // Convert desc to a matrix if (SIFT3D_Descriptor_store_to_Mat_rm(desc, &mat)) goto desc2mx_quit; // Convert the matrix to an mxArray if ((mx = mat2mx(&mat)) == NULL) goto desc2mx_quit; // Clean up cleanup_Mat_rm(&mat); return mx; desc2mx_quit: cleanup_Mat_rm(&mat); return NULL; } /* Converts a pair of mxArrays to a SIFT3D_Descriptor_store. */ int mx2desc(const mxArray *const mx, SIFT3D_Descriptor_store *const desc) { Mat_rm mat; // Initialize intermediates if (init_Mat_rm(&mat, 0, 0, SIFT3D_FLOAT, SIFT3D_FALSE)) return SIFT3D_FAILURE; // Convert the mxArrays to a matrix if (mx2mat(mx, &mat)) { SIFT3D_ERR("mx2desc: Failed to convert mx to mat"); goto mx2desc_quit; } // Convert the matrix to a SIFT3D_Descriptor_store if (Mat_rm_to_SIFT3D_Descriptor_store(&mat, desc)) { SIFT3D_ERR("mx2desc: Failed to convert mat to desc"); goto mx2desc_quit; } // Clean up cleanup_Mat_rm(&mat); return SIFT3D_SUCCESS; mx2desc_quit: cleanup_Mat_rm(&mat); return SIFT3D_FAILURE; } /* Convert an array of doubles to an mxArray. * * Parameters: * array: The array. * num: The number of elements. * * Returns a pointer to the mxArray, or NULL on failure. */ mxArray *array2mx(const double *const array, const size_t len) { mxArray *mx; double *mxData; size_t i; const mwSize dims[] = {len}; const int ndim = 1; // Create the mxArray if ((mx = mxCreateNumericArray(ndim, dims, mxDOUBLE_CLASS, mxREAL)) == NULL) return NULL; // Get a pointer to the array's data if ((mxData = mxGetData(mx)) == NULL) return NULL; // Copy the data for (i = 0; i < len; i++) { mxData[i] = array[i]; } return mx; } /* Wrapper for SIFT3D_detect_keypoints. */ int mex_SIFT3D_detect_keypoints(const Image *const im, Keypoint_store *const kp) { return SIFT3D_detect_keypoints(®.sift3d, im, kp); } /* Wrapper for SIFT3D_assign_orientations. */ int mex_SIFT3D_assign_orientations(const Image *const im, Keypoint_store *const kp, double **const conf) { return SIFT3D_assign_orientations(®.sift3d, im, kp, conf); } /* Wrapper for SIFT3D_extract_descriptors. */ int mex_SIFT3D_extract_descriptors(const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc) { return SIFT3D_extract_descriptors(®.sift3d, kp, desc); } /* Wrapper for SIFT3D_extract_raw_descriptors. */ int mex_SIFT3D_extract_raw_descriptors(const Image *const im, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc) { return SIFT3D_extract_raw_descriptors(®.sift3d, im, kp, desc); } /* Wrapper for SIFT3D_have_gpyr. */ int mexHaveGpyr(void) { return SIFT3D_have_gpyr(®.sift3d); } /* Wrapper to set the options for the SIFT3D struct. The argument mx shall be * a struct with the following fields: * -peakThresh * -cornerThresh * -numKpLevels * -sigmaN * -sigma0 * * Any other fields are ignored. Empty fields are replaced with the defaults. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int mex_set_opts_SIFT3D(const mxArray *const mx) { SIFT3D sift3d; const mxArray *mxFirstOctave, *mxPeakThresh, *mxCornerThresh, *mxNumKpLevels, *mxSigmaN, *mxSigma0; // Verify inputs if (mxIsEmpty(mx) || !mxIsStruct(mx)) return SIFT3D_FAILURE; // Get the option arrays if ((mxPeakThresh = mxGetField(mx, 0, "peakThresh")) == NULL || (mxCornerThresh = mxGetField(mx, 0, "cornerThresh")) == NULL || (mxNumKpLevels = mxGetField(mx, 0, "numKpLevels")) == NULL || (mxSigmaN = mxGetField(mx, 0, "sigmaN")) == NULL || (mxSigma0 = mxGetField(mx, 0, "sigma0")) == NULL) return SIFT3D_FAILURE; // Initialize intermediates if (init_SIFT3D(&sift3d)) return SIFT3D_FAILURE; // Set the non-empty options in our new SIFT3D struct if (!mxIsEmpty(mxPeakThresh) && set_peak_thresh_SIFT3D(&sift3d, mxGetScalar(mxPeakThresh))) goto mex_set_opts_SIFT3D_quit; if (!mxIsEmpty(mxCornerThresh) && set_corner_thresh_SIFT3D(&sift3d, mxGetScalar(mxCornerThresh))) goto mex_set_opts_SIFT3D_quit; if (!mxIsEmpty(mxNumKpLevels) && set_num_kp_levels_SIFT3D(&sift3d, (int) mxGetScalar(mxNumKpLevels))) goto mex_set_opts_SIFT3D_quit; if (!mxIsEmpty(mxSigmaN) && set_sigma_n_SIFT3D(&sift3d, mxGetScalar(mxSigmaN))) goto mex_set_opts_SIFT3D_quit; if (!mxIsEmpty(mxSigma0) && set_sigma0_SIFT3D(&sift3d, mxGetScalar(mxSigma0))) goto mex_set_opts_SIFT3D_quit; // Set the options in the Reg_SIFT3D struct if (set_SIFT3D_Reg_SIFT3D(®, &sift3d)) goto mex_set_opts_SIFT3D_quit; // Clean up cleanup_SIFT3D(&sift3d); return SIFT3D_SUCCESS; mex_set_opts_SIFT3D_quit: cleanup_SIFT3D(&sift3d); return SIFT3D_FAILURE; } /* Wrapper to set the options for the Reg_SIFT3D struct. The argument mx shall * be a struct with the following fields: * -numIter * -errThresh * -nnThresh * * Any other fields are ignored. Empty fields are replaced with the defaults. * * Returns SIFT3D_SUCCESS on success, SIFT3D_FAILURE otherwise. */ int mex_set_opts_Reg_SIFT3D(const mxArray *const mx) { Ransac ran; const mxArray *mxNumIter, *mxErrThresh, *mxNnThresh; double nn_thresh; // Verify inputs if (mxIsEmpty(mx) || !mxIsStruct(mx)) return SIFT3D_FAILURE; // Initialize options to the defaults nn_thresh = SIFT3D_nn_thresh_default; init_Ransac(&ran); // Get the option arrays if ((mxNumIter = mxGetField(mx, 0, "numIter")) == NULL || (mxErrThresh = mxGetField(mx, 0, "errThresh")) == NULL || (mxNnThresh = mxGetField(mx, 0, "nnThresh")) == NULL) return SIFT3D_FAILURE; // Get the non-empty options if (!mxIsEmpty(mxNumIter) && set_num_iter_Ransac(&ran, (int) mxGetScalar(mxNumIter))) { return SIFT3D_FAILURE; } if (!mxIsEmpty(mxErrThresh) && set_err_thresh_Ransac(&ran, mxGetScalar(mxErrThresh))) { return SIFT3D_FAILURE; } if (!mxIsEmpty(mxNnThresh)) { nn_thresh = mxGetScalar(mxNnThresh); } // Set the options return set_Ransac_Reg_SIFT3D(®, &ran) || set_nn_thresh_Reg_SIFT3D(®, nn_thresh); } /* Get the current value of nn_thresh from the Reg_SIFT3D struct. */ double mex_get_nn_thresh_Reg_SIFT3D(void) { return reg.nn_thresh; } /* Wrapper for register_SIFT3D_resample */ int mex_register_SIFT3D_resample(const Image *const src, const Image *const ref, const interp_type interp, void *const tform) { return register_SIFT3D_resample(®, src, ref, interp, tform); } /* Wrapper for set_src_Reg_SIFT3D */ int mex_set_src_Reg_SIFT3D(const Image *const src) { return set_src_Reg_SIFT3D(®, src); } /* Wrapper for set_ref_Reg_SIFT3D */ int mex_set_ref_Reg_SIFT3D(const Image *const ref) { return set_ref_Reg_SIFT3D(®, ref); } /* Wrapper for register_SIFT3D */ int mex_register_SIFT3D(void *const tform) { return register_SIFT3D(®, tform); } /* Wrapper for get_matches_Reg_SIFT3D */ int mex_get_matches_Reg_SIFT3D(Mat_rm *const match_src, Mat_rm *const match_ref) { return get_matches_Reg_SIFT3D(®, match_src, match_ref); } ================================================ FILE: wrappers/matlab/mexutil.h ================================================ /* ----------------------------------------------------------------------------- * mexutil.h * ----------------------------------------------------------------------------- * Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. * ----------------------------------------------------------------------------- * Public header for SIFT3D mex utility library. * ----------------------------------------------------------------------------- */ #include #include "mex.h" #include "imtypes.h" #ifndef _MEXUTIL_H #define _MEXUTIL_H #ifdef __cplusplus extern "C" { #endif void err_msg(const char *name, const char *msg); void err_msgu(const char *name, const char *msg); int isDouble(const mxArray *const mx); mwIndex mxImGetIdx(const mxArray *const mx, const int x, const int y, const int z, const int c); mxArray *im2mx(const Image *const im); int mx2im(const mxArray *const mx, Image *const im); mxArray *units2mx(const Image *const im); int mx2units(const mxArray *const mx, Image *const im); int mx2imWithUnits(const mxArray *const data, const mxArray *const units, Image *const im); mxArray *mat2mx(const Mat_rm *const mat); int mx2mat(const mxArray *const mx, Mat_rm *const mat); mxArray *kp2mx(const Keypoint_store *const); int mx2kp(const mxArray *const mx, Keypoint_store *const); mxArray *desc2mx(const SIFT3D_Descriptor_store *const desc); int mx2desc(const mxArray *const mx, SIFT3D_Descriptor_store *const desc); mxArray *array2mx(const double *const array, const size_t len); int mex_SIFT3D_detect_keypoints(const Image *const im, Keypoint_store *const kp); int mex_SIFT3D_assign_orientations(const Image *const im, Keypoint_store *const kp, double **const conf); int mex_SIFT3D_extract_descriptors(const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc); int mex_SIFT3D_extract_raw_descriptors(const Image *const im, const Keypoint_store *const kp, SIFT3D_Descriptor_store *const desc); int mexHaveGpyr(void); int mex_set_opts_SIFT3D(const mxArray *const mx); int mex_set_opts_Reg_SIFT3D(const mxArray *const mx); int mex_set_nn_thresh_Reg_SIFT3D(const double nn_thresh); double mex_get_nn_thresh_Reg_SIFT3D(void); int mex_register_SIFT3D_resample(const Image *const src, const Image *const ref, const interp_type interp, void *const tform); int mex_set_src_Reg_SIFT3D(const Image *const src); int mex_set_ref_Reg_SIFT3D(const Image *const ref); int mex_register_SIFT3D(void *const tform); int mex_get_matches_Reg_SIFT3D(Mat_rm *const match_src, Mat_rm *const match_ref); #ifdef __cplusplus } #endif #endif ================================================ FILE: wrappers/matlab/orientation3D.m ================================================ %orientation3D(keys, im, units) Assign 3D orientations to keypoints % in an image. This is not needed if your keypoints were detected with % detectSift3D, but may be useful if your keypoints were defined % manually with keypoint3D. % % Arguments: % keys - [Mx1] array of keypoint structs. See keypoint3D.m. % im - An [MxNxP] array, where voxels are indexed in (x, y, z) order. % units - (Optional) See imRead3D. (Default: [1 1 1]) % % Return values: % keys - The same as the input, but with the "ori" fields filled with % rotation matrices assigned based on the data in im. % conf - An [Mx1] vector of confidence scores. The scores are in the % interval [0, 1], where 1 is the most confident, 0 is the least. A % higher score means the assigned orientation is more likely to be % robust. A negative score means the orientation could not be assigned. % % Note: For some data, this function cannot find a stable orientation, in % which case it throws a warning and returns an identity matrix R. The conf % score for these elements is negative. % % Example: % im = rand(20, 20, 20); % keys = keypoint3D([10 10 10]); % [keys, conf] = orientation3D(keys, im); % % See also: % keypoint3D, extractSift3D, imRead3D, setupSift3D % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. function [R, conf] = orientation3D(keys, im, units) % Verify inputs if isempty(im) error('im is empty') end if ndims(im) ~= 3 error(['im must have 3 dimensions, detected ' num2str(ndims(im))]); end if nargin < 3 || isempty(units) units = []; end units = checkUnits3D(units); % Scale and convert the image to single precision im = single(im); im = im / max(im(:)); % Assign the orientations [R, conf] = mexOrientation3D(keys, im, units); % Check the validity if any(conf) < 0 warning('Failed to assign a stable orientation to some keypoints') end end ================================================ FILE: wrappers/matlab/registerSift3D.m ================================================ function [A, matchSrc, matchRef] = registerSift3D(src, ref, varargin) %registerSift3D(src, ref, options) register a pair of % images using SIFT3D keypoints. Detects keypoints, extracts and matches % descriptors, and then fits an affine transformation using the RANSAC % algorithm. % % Arguments: % src, ref - A pair of [MxNxP] arrays, where voxels are indexed in % (x, y, z) order. src is the "source" or "moving" image, and ref is % the "reference" or "fixed" image. % % Options: % srcUnits, refUnits - The physical units for src and ref, % respectively. See imRead3D for the format. Units should be provided % when attempting to register images of different resolutions, i.e. % 5mm slices to 1mm slices. (Default: [1 1 1]) % nnThresh - The matching threshold, in the interval (0, 1]. See % matchSift3D for details. (Default: 0.8) % errThresh - The RANSAC inlier threshold, in the interval (0, inf). % This is a threshold on the squared Euclidean distance in real-world % units. (Default: 5.0) % numIter - The number of RANSAC iterations. (Default: 500) % resample - If true, resamples src and ref to have the same resolution % prior to registration. This is slow. Use it when the inputs have % vastly different units. (Default: false) % % SIFT3D options: % This function also accepts the following options controlling keypoint % detection: % peakThresh % cornerThresh % numKpLevels % sigmaN % sigma0 % % See detectSift3D.m for the meaning and format of these options. % % Return values: % A - A [4x3] matrix giving an affine transformation from the % coordinates of ref to those of src. The transformation can be % applied as follows: % [xt yt zt]' = A * [x y z 1]'; % matchSrc, matchRef - A pair of [3xQ] arrays giving the matched % keypoint locations in src and ref, respectively. Each row is an % (x, y, z) coordinate, so that the ith row of matchSrc matches the % ith row of matchRef. These matches may contain outliers. % % Note: This function will reserve memory that persists after it has % finished. To release all SIFT3D memory, use 'clear mex'. % % Examples: % % Load the images % [src, srcUnits] = imRead3D('src.dcm'); % [ref, refUnits] = imRead3D('ref.dcm'); % % % Register with units % [A, matchSrc, matchRef] = registerSift3D(src, ref, 'srcUnits', ... % srcUnits, 'refUnits', refUnits, 'resample', true); % % % Register without units % [A, matchSrc, matchRef] = registerSift3D(src, ref); % % % See also: % imRead3D, imWrite3D, matchSift3D, setupSift3D % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Option names srcUnitsStr = 'srcUnits'; refUnitsStr = 'refUnits'; numIterStr = 'numIter'; nnThreshStr = 'nnThresh'; errThreshStr = 'errThresh'; resampleStr = 'resample'; % Parse options parser = Sift3DParser; parser.addParamValue(srcUnitsStr, []) parser.addParamValue(refUnitsStr, []) parser.addParamValue(numIterStr, []) parser.addParamValue(nnThreshStr, []) parser.addParamValue(errThreshStr, []) parser.addParamValue(resampleStr, false) sift3DOpts = parser.parseAndVerify(varargin{:}); srcUnits = parser.Results.srcUnits; refUnits = parser.Results.refUnits; numIter = parser.Results.numIter; nnThresh = parser.Results.nnThresh; errThresh = parser.Results.errThresh; resample = parser.Results.resample; % Verify inputs narginchk(2, inf) if isempty(src) error('src is empty') end if isempty(ref) error('ref is empty') end srcUnits = checkUnits3D(srcUnits, 'srcUnits'); refUnits = checkUnits3D(refUnits, 'refUnits'); if ~isempty(numIter) validateattributes(numIter, {'numeric'}, ... {'real', 'scalar', '>', 0}, 'numIter') end if ~isempty(nnThresh) validateattributes(nnThresh, {'numeric'}, ... {'real', 'scalar', '>=', 0, '<', 1}, 'nnThresh') end if ~isempty(errThresh) validateattributes(errThresh, {'numeric'}, ... {'real', 'scalar', '>', 0}, 'errThresh') end validateattributes(resample, {'logical'}, {'scalar'}, 'resample') % Convert the images to single precision and scale to [0, 1] src = imFormat(src); ref = imFormat(ref); % Collect the options in a struct regOpts = struct(numIterStr, numIter, ... nnThreshStr, nnThresh, ... errThreshStr, errThresh); % Register [A, matchSrc, matchRef] = mexRegisterSift3D(src, ref, srcUnits, ... refUnits, resample, regOpts, sift3DOpts); end % Helper function to convert images to single precision and scale to [0,1] function im = imFormat(im) im = single(im); im = im / (max(im(:)) + eps); end ================================================ FILE: wrappers/matlab/setupSift3D.m ================================================ % setupSift3D % % Run this script to set up the paths for the SIFT3D matlab toolbox. For % example, add the following line to your startup.m file: % % run('/path/to/sift3d/lib/wrappers/matlab/setupSift3D.m') % % where /path/to/sift3d/ is the path to the your SIFT3D installation. % % See also: % imRead3D, imWrite3D, detectSift3D, extractSift3D, keypoint3D % % Copyright (c) 2015-2016 Blaine Rister et al., see LICENSE for details. % Get the full path to this file filename = mfilename('fullpath'); % Extract the toolbox directory toolboxname = fileparts(filename); % Add the toolbox to the path addpath(genpath(toolboxname)); % Clean up the workspace clear filename toolboxname