├── .gitmodules ├── CMake ├── Tube-Segmentation-FrameworkConfig.cmake.in ├── Tube-Segmentation-FrameworkUse.cmake └── tsf-config.h.in ├── CMakeLists.txt ├── LICENSE ├── README.md ├── commons.hpp ├── eigenanalysisOfHessian.cpp ├── eigenanalysisOfHessian.hpp ├── globalCenterlineExtraction.cpp ├── globalCenterlineExtraction.hpp ├── gradientVectorFlow.cpp ├── gradientVectorFlow.hpp ├── inputOutput.cpp ├── inputOutput.hpp ├── kernels.cl ├── kernels_no_3d_write.cl ├── main.cpp ├── parallelCenterlineExtraction.cpp ├── parallelCenterlineExtraction.hpp ├── parameters.cpp ├── parameters.hpp ├── parameters ├── centerline-gpu │ ├── AAA-Phantom-CT │ ├── AAA-Vessels-CT │ ├── Liver-Vessels-CT │ ├── Liver-Vessels-MR │ ├── Lung-Airways-CT │ ├── Neuro-Vessels-MRA │ ├── Neuro-Vessels-USA │ ├── Phantom-Acc-US │ └── Synthetic-Vascusynth ├── centerline-ridge │ ├── Lung-Airways-CT │ ├── Neuro-Vessels-MRA │ ├── Neuro-Vessels-USA │ └── Synthetic-Vascusynth ├── centerline-test │ ├── Lung-Airways-CT │ ├── Neuro-Vessels-MRA │ ├── Neuro-Vessels-USA │ ├── Phantom-Acc-US │ └── Synthetic-Vascusynth └── parameters ├── ridgeTraversalCenterlineExtraction.cpp ├── ridgeTraversalCenterlineExtraction.hpp ├── segmentation.cpp ├── segmentation.hpp ├── tests ├── CMakeLists.txt ├── TSFOutputTests.cpp ├── clinicalTests.cpp ├── data │ └── synthetic │ │ ├── dataset_1 │ │ ├── noisy.mhd │ │ ├── noisy0.raw │ │ ├── original.mhd │ │ ├── original.raw │ │ ├── real_centerline.mhd │ │ └── real_centerline.raw │ │ ├── dataset_2 │ │ ├── centerline.raw │ │ ├── noisy.mhd │ │ ├── noisy0.raw │ │ ├── original.mhd │ │ ├── original.raw │ │ └── real_centerline.mhd │ │ ├── dataset_3 │ │ ├── centerline.raw │ │ ├── noisy.mhd │ │ ├── noisy0.raw │ │ ├── original.mhd │ │ ├── original.raw │ │ └── real_centerline.mhd │ │ └── dataset_4 │ │ ├── original.mhd │ │ └── original.raw ├── parameterTests.cpp ├── tests.cpp ├── tests.hpp ├── tubeSegmentationTests.cpp └── tubeValidation.cpp ├── timing.hpp ├── tube-segmentation.cpp ├── tube-segmentation.hpp ├── tubeDetectionFilters.cpp └── tubeDetectionFilters.hpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SIPL"] 2 | path = SIPL 3 | url = https://github.com/smistad/SIPL.git 4 | [submodule "OpenCLUtilityLibrary"] 5 | path = OpenCLUtilityLibrary 6 | url = https://github.com/smistad/OpenCLUtilityLibrary.git 7 | -------------------------------------------------------------------------------- /CMake/Tube-Segmentation-FrameworkConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # Find the Tube-Segmentation-Framework includes and library 2 | # 3 | # Tube-Segmentation-Framework_INCLUDE_DIR - where to find include files. 4 | # Tube-Segmentation-Framework_LIBRARIES - List of fully qualified libraries to link against. 5 | # Tube-Segmentation-Framework_FOUND - set to 1 if found. 6 | # 7 | # Usage: 8 | # Tube-Segmentation-Framework (Tube-Segmentation-Framework) 9 | #---------- 10 | # option(CX_USE_Tube-Segmentation-Framework "use Tube-Segmentation-Framework (Tube-Segmentation-Framework)" OFF) 11 | # if (CX_USE_Tube-Segmentation-Framework) 12 | # ADD_DEFINITIONS(-DCX_USE_Tube-Segmentation-Framework) 13 | # find_package(Tube-Segmentation-Framework REQUIRED) 14 | # include(${Tube-Segmentation-Framework_USE_FILE}) 15 | # endif() 16 | # 17 | 18 | set (Tube-Segmentation-Framework_FOUND 1) 19 | 20 | set (Tube-Segmentation-Framework_BINARY_DIR "@Tube-Segmentation-Framework_BINARY_DIR@") 21 | 22 | set (Tube-Segmentation-Framework_INCLUDE_DIRS "@Tube-Segmentation-Framework_INCLUDE_DIRS@") 23 | set (Tube-Segmentation-Framework_LIBRARY_DIRS "@Tube-Segmentation-Framework_LIBRARY_DIRS@") 24 | 25 | set(Tube-Segmentation-Framework_PARAMETERS_DIR "@PARAMETERS_DIR@") 26 | set(Tube-Segmentation-Framework_KERNELS_DIR "@KERNELS_DIR@") 27 | 28 | set (Tube-Segmentation-Framework_USE_FILE "@Tube-Segmentation-Framework_SOURCE_DIR@/CMake/Tube-Segmentation-FrameworkUse.cmake") 29 | 30 | -------------------------------------------------------------------------------- /CMake/Tube-Segmentation-FrameworkUse.cmake: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## Tube-Segmentation-Framework Use file 3 | ########################################################### 4 | 5 | #------------------------------------------------------------------------------ 6 | # External libraries 7 | #------------------------------------------------------------------------------ 8 | # Boost 9 | find_package(Boost REQUIRED) 10 | 11 | # GTK 12 | #find_package (PkgConfig REQUIRED) 13 | #pkg_check_modules (GTK2 REQUIRED gtk+-2.0 gthread-2.0) 14 | 15 | # SIPL 16 | find_package(SIPL PATHS "${Tube-Segmentation-Framework_BINARY_DIR}/SIPL" REQUIRED) 17 | include(${SIPL_USE_FILE}) 18 | 19 | # OpenCLUtilities 20 | find_package(OCL-Utilities PATHS "${Tube-Segmentation-Framework_BINARY_DIR}/OpenCLUtilities" REQUIRED) 21 | include(${OCL-Utilities_USE_FILE}) 22 | 23 | #------------------------------------------------------------------------------ 24 | # Where to look for includes and libraries 25 | #------------------------------------------------------------------------------ 26 | include_directories( ${Tube-Segmentation-Framework_INCLUDE_DIRS} ${Tube-Segmentation-Framework_BINARY_DIR} ${Boost_INCLUDE_DIRS}) 27 | link_directories (${Tube-Segmentation-Framework_LIBRARY_DIRS}) 28 | 29 | -------------------------------------------------------------------------------- /CMake/tsf-config.h.in: -------------------------------------------------------------------------------- 1 | // directory containing parameter files 2 | #define PARAMETERS_DIR "@PARAMETERS_DIR@" 3 | 4 | // directory containing kernel files 5 | #define KERNELS_DIR "@KERNELS_DIR@" 6 | 7 | // directory containing test data 8 | #define TESTDATA_DIR "@TESTDATA_DIR@" -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## Tube-Segmentation-Framework project 3 | ########################################################### 4 | project(Tube-Segmentation-Framework) 5 | 6 | cmake_minimum_required(VERSION 2.8) 7 | 8 | # 9 | # Set compiler flags 10 | ########### 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3") 12 | option(USE_C++11 "use C++11 (Tube-Segmentation-Framework)" ON) # cmake option to use boost libraries instead of C++11 13 | if(USE_C++11) 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D CPP11 -std=c++0x") 15 | endif() 16 | 17 | #------------------------------------------------------------------------------ 18 | # External libraries 19 | #------------------------------------------------------------------------------ 20 | 21 | # 22 | # OpenCLUtilityLibrary 23 | ########### 24 | add_subdirectory(OpenCLUtilityLibrary) 25 | find_package(OpenCLUtilityLibrary PATHS "${Tube-Segmentation-Framework_BINARY_DIR}/OpenCLUtilityLibrary" REQUIRED) 26 | include(${OpenCLUtilityLibrary_USE_FILE}) 27 | 28 | # 29 | # SIPL 30 | ########### 31 | add_subdirectory(SIPL) 32 | find_package(SIPL PATHS "${Tube-Segmentation-Framework_BINARY_DIR}/SIPL" REQUIRED) 33 | include(${SIPL_USE_FILE}) 34 | 35 | # 36 | # OpenMP 37 | ########### 38 | find_package(OpenMP) 39 | if(OPENMP_FOUND) 40 | message("-- OpenMP was detected. Using OpenMP to speed up some calculations.") 41 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}" ) 42 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}" ) 43 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}" ) 44 | endif() 45 | 46 | # 47 | # Boost 48 | ########### 49 | find_package(Boost COMPONENTS iostreams REQUIRED) 50 | 51 | #------------------------------------------------------------------------------ 52 | # Where to look for includes and libraries 53 | #------------------------------------------------------------------------------ 54 | include_directories(${Boost_INCLUDE_DIRS} ${PROJECT_BINARY_DIR}) 55 | link_directories(${Boost_LIBRARY_DIRS}) 56 | 57 | 58 | #------------------------------------------------------------------------------ 59 | # Targets 60 | #------------------------------------------------------------------------------ 61 | 62 | # 63 | # tubeSegmentationLib library 64 | ########### 65 | add_library(tubeSegmentationLib 66 | tube-segmentation.cpp 67 | parameters.cpp 68 | gradientVectorFlow.cpp 69 | tubeDetectionFilters.cpp 70 | ridgeTraversalCenterlineExtraction.cpp 71 | eigenanalysisOfHessian.cpp 72 | globalCenterlineExtraction.cpp 73 | parallelCenterlineExtraction.cpp 74 | inputOutput.cpp 75 | segmentation.cpp 76 | ) 77 | target_link_libraries(tubeSegmentationLib OpenCLUtilityLibrary SIPL ${Boost_LIBRARIES} ${OPENCL_LIBRARIES}) 78 | 79 | # 80 | # tubeSegmentation executable 81 | ########### 82 | if(SIPL_USE_GTK) 83 | add_executable(tubeSegmentation 84 | main.cpp 85 | tube-segmentation.cpp 86 | parameters.cpp 87 | gradientVectorFlow.cpp 88 | tubeDetectionFilters.cpp 89 | ridgeTraversalCenterlineExtraction.cpp 90 | eigenanalysisOfHessian.cpp 91 | globalCenterlineExtraction.cpp 92 | parallelCenterlineExtraction.cpp 93 | inputOutput.cpp 94 | segmentation.cpp 95 | ) 96 | target_link_libraries(tubeSegmentation SIPL OpenCLUtilityLibrary ${Boost_LIBRARIES} ${OPENCL_LIBRARIES}) 97 | endif() 98 | 99 | #------------------------------------------------------------------------------ 100 | # Testing 101 | #------------------------------------------------------------------------------ 102 | find_package(GTest) 103 | if(GTEST_FOUND) 104 | if(SIPL_USE_GTK) 105 | message("Google test framework found. Enabling testing ...") 106 | target_link_libraries(tubeSegmentationLib OpenCLUtilityLibrary SIPL ${Boost_LIBRARIES}) 107 | enable_testing() 108 | add_subdirectory(tests) 109 | endif() 110 | endif() 111 | 112 | #------------------------------------------------------------------------------ 113 | # Set variables 114 | #------------------------------------------------------------------------------ 115 | 116 | set(PARAMETERS_DIR ${PROJECT_SOURCE_DIR}/parameters) 117 | set(KERNELS_DIR ${PROJECT_SOURCE_DIR}) 118 | set(TESTDATA_DIR ${PROJECT_SOURCE_DIR}/tests/data) 119 | 120 | #------------------------------------------------------------------------------ 121 | # Configure file for find_package module 122 | #------------------------------------------------------------------------------ 123 | 124 | set( Tube-Segmentation-Framework_INCLUDE_DIRS 125 | ${Tube-Segmentation-Framework_SOURCE_DIR} 126 | ) 127 | 128 | set(Tube-Segmentation-Framework_LIBRARY_DIRS 129 | ${Tube-Segmentation-Framework_BINARY_DIR} 130 | ${OCL-Utilities_LIBRARY_DIRS} 131 | ${SIPL_BINARY_LIBRARY_DIRS} 132 | ) 133 | 134 | configure_file ( 135 | "${PROJECT_SOURCE_DIR}/CMake/Tube-Segmentation-FrameworkConfig.cmake.in" 136 | "${PROJECT_BINARY_DIR}/Tube-Segmentation-FrameworkConfig.cmake" 137 | ) 138 | 139 | #------------------------------------------------------------------------------ 140 | # Configure file for settings, filepaths, parameters ... 141 | #------------------------------------------------------------------------------ 142 | 143 | configure_file( 144 | "${PROJECT_SOURCE_DIR}/CMake/tsf-config.h.in" 145 | "${PROJECT_BINARY_DIR}/tsf-config.h" 146 | ) 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Erik Smistad. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY Erik Smistad ''AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Erik Smistad OR 16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | The views and conclusions contained in the software and documentation are those of the 24 | authors and should not be interpreted as representing official policies, either expressed 25 | or implied, of Erik Smistad. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tube Segmentation Framework 2 | =================================== 3 | 4 | The tube segmentation framework is a software for fast segmentation and centerline extraction of tubular structures (e.g. blood vessels and airways) from different modalities (e.g. CT, MR and US) and organs using GPUs and OpenCL. 5 | 6 | For details about the implementation see the following publication: 7 | [GPU accelerated segmentation and centerline extraction of tubular structures in medical images](http://link.springer.com/article/10.1007%2Fs11548-013-0956-x) 8 | Erik Smistad, Anne C. Elster and Frank Lindseth 9 | International Journal of Computer Assisted Radiology and Surgery 10 | 2013 11 | 12 | If you use this software in any publications, please cite our article. 13 | 14 | See the file LICENSE for license information. 15 | 16 | Dependencies 17 | ---------------------------------- 18 | 19 | * OpenCL. You need an OpenCL implementation installed on your system to use this software (AMD, NVIDIA, Intel or Apple) 20 | * Boost iostreams. E.g. on Ubuntu linux use the following package: libboost-iostreams-dev 21 | * The two submodules: SIPL and OpenCLUtilities 22 | * GTK 2 for visualization (not required, used by the SIPL module). On Ubuntu linux use the following package: libgtk2.0-dev 23 | 24 | Compiling 25 | ---------------------------------- 26 | 27 | To compile the software first make sure that all software dependencies are installed and set up correctly. 28 | Next, use cmake. 29 | 30 | For instance on linux, do the following: 31 | ```bash 32 | cmake . 33 | make -j8 34 | ``` 35 | 36 | Usage 37 | ---------------------------------- 38 | 39 | To see the help message use the software with no arguments. 40 | `./tubeSegmentation` 41 | 42 | The first arguments is the dataset to process. This has to be a metadata (.mhd) file. 43 | 44 | Some test data is available with the software. You can test the program with the following command: 45 | ```bash 46 | ./tubeSegmentation tests/data/synthetic/dataset_1/noisy.mhd --parameters Synthetic-Vascusynth --display 47 | ``` 48 | 49 | 50 | Parameters 51 | ---------------------------------- 52 | 53 | This software has a lot of parameters and several parameter presets are available: 54 | * Lung-Airways-CT 55 | * Neuro-Vessels-USA 56 | * Neuro-Vessels-MRA 57 | * AAA-Vessels-CT 58 | * Liver-Vessels-CT 59 | * Synthetic-Vascusynth 60 | 61 | The parameter preset is set with the program argument "--parameters ". 62 | 63 | Tests 64 | ---------------------------------- 65 | 66 | This software contains several tests written with Google Test. 67 | Install Google Test and run cmake again to compile the tests. 68 | Run the tests using the command `./tests/runTests` 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /commons.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMMONS_H 2 | #define COMMONS_H 3 | #define CL_USE_DEPRECATED_OPENCL_1_1_APIS 4 | 5 | #include "OpenCLUtilityLibrary/OpenCLManager.hpp" 6 | #include "SIPL/Types.hpp" 7 | 8 | typedef struct OpenCL { 9 | cl::Context context; 10 | cl::CommandQueue queue; 11 | cl::Program program; 12 | cl::Device device; 13 | cl::Platform platform; 14 | oul::GarbageCollector * GC; 15 | } OpenCL; 16 | 17 | #ifdef WIN32 18 | // Add some math functions that are missing from the windows math library 19 | template 20 | static inline double log2(T a) { 21 | return log((double)a)/log(2.0); 22 | } 23 | 24 | template 25 | static inline double round(T a) { 26 | return floor((double)a+0.5); 27 | } 28 | #endif 29 | 30 | static inline bool inBounds(SIPL::int3 pos, SIPL::int3 size) { 31 | return pos.x > 0 && pos.y > 0 && pos.z > 0 && pos.x < size.x && pos.y < size.y && pos.z < size.z; 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /eigenanalysisOfHessian.cpp: -------------------------------------------------------------------------------- 1 | #include "eigenanalysisOfHessian.hpp" 2 | #include "SIPL/Types.hpp" 3 | using namespace SIPL; 4 | #define MAX(a,b) a > b ? a : b 5 | 6 | 7 | 8 | #define SIZE 3 9 | 10 | float hypot2(float x, float y) { 11 | return sqrt(x*x+y*y); 12 | } 13 | 14 | // Symmetric Householder reduction to tridiagonal form. 15 | 16 | void tred2(float V[SIZE][SIZE], float d[SIZE], float e[SIZE]) { 17 | 18 | // This is derived from the Algol procedures tred2 by 19 | // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 20 | // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 21 | // Fortran subroutine in EISPACK. 22 | 23 | for (int j = 0; j < SIZE; j++) { 24 | d[j] = V[SIZE-1][j]; 25 | } 26 | 27 | // Householder reduction to tridiagonal form. 28 | 29 | for (int i = SIZE-1; i > 0; i--) { 30 | 31 | // Scale to avoid under/overflow. 32 | 33 | float scale = 0.0f; 34 | float h = 0.0f; 35 | for (int k = 0; k < i; k++) { 36 | scale = scale + fabs(d[k]); 37 | } 38 | if (scale == 0.0f) { 39 | e[i] = d[i-1]; 40 | for (int j = 0; j < i; j++) { 41 | d[j] = V[i-1][j]; 42 | V[i][j] = 0.0f; 43 | V[j][i] = 0.0f; 44 | } 45 | } else { 46 | 47 | // Generate Householder vector. 48 | 49 | for (int k = 0; k < i; k++) { 50 | d[k] /= scale; 51 | h += d[k] * d[k]; 52 | } 53 | float f = d[i-1]; 54 | float g = sqrt(h); 55 | if (f > 0) { 56 | g = -g; 57 | } 58 | e[i] = scale * g; 59 | h = h - f * g; 60 | d[i-1] = f - g; 61 | for (int j = 0; j < i; j++) { 62 | e[j] = 0.0f; 63 | } 64 | 65 | // Apply similarity transformation to remaining columns. 66 | 67 | for (int j = 0; j < i; j++) { 68 | f = d[j]; 69 | V[j][i] = f; 70 | g = e[j] + V[j][j] * f; 71 | for (int k = j+1; k <= i-1; k++) { 72 | g += V[k][j] * d[k]; 73 | e[k] += V[k][j] * f; 74 | } 75 | e[j] = g; 76 | } 77 | f = 0.0f; 78 | for (int j = 0; j < i; j++) { 79 | e[j] /= h; 80 | f += e[j] * d[j]; 81 | } 82 | float hh = f / (h + h); 83 | for (int j = 0; j < i; j++) { 84 | e[j] -= hh * d[j]; 85 | } 86 | for (int j = 0; j < i; j++) { 87 | f = d[j]; 88 | g = e[j]; 89 | for (int k = j; k <= i-1; k++) { 90 | V[k][j] -= (f * e[k] + g * d[k]); 91 | } 92 | d[j] = V[i-1][j]; 93 | V[i][j] = 0.0f; 94 | } 95 | } 96 | d[i] = h; 97 | } 98 | 99 | // Accumulate transformations. 100 | 101 | for (int i = 0; i < SIZE-1; i++) { 102 | V[SIZE-1][i] = V[i][i]; 103 | V[i][i] = 1.0f; 104 | float h = d[i+1]; 105 | if (h != 0.0f) { 106 | for (int k = 0; k <= i; k++) { 107 | d[k] = V[k][i+1] / h; 108 | } 109 | for (int j = 0; j <= i; j++) { 110 | float g = 0.0f; 111 | for (int k = 0; k <= i; k++) { 112 | g += V[k][i+1] * V[k][j]; 113 | } 114 | for (int k = 0; k <= i; k++) { 115 | V[k][j] -= g * d[k]; 116 | } 117 | } 118 | } 119 | for (int k = 0; k <= i; k++) { 120 | V[k][i+1] = 0.0f; 121 | } 122 | } 123 | for (int j = 0; j < SIZE; j++) { 124 | d[j] = V[SIZE-1][j]; 125 | V[SIZE-1][j] = 0.0f; 126 | } 127 | V[SIZE-1][SIZE-1] = 1.0f; 128 | e[0] = 0.0f; 129 | } 130 | 131 | // Symmetric tridiagonal QL algorithm. 132 | 133 | void tql2(float V[SIZE][SIZE], float d[SIZE], float e[SIZE]) { 134 | 135 | // This is derived from the Algol procedures tql2, by 136 | // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 137 | // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 138 | // Fortran subroutine in EISPACK. 139 | 140 | for (int i = 1; i < SIZE; i++) { 141 | e[i-1] = e[i]; 142 | } 143 | e[SIZE-1] = 0.0f; 144 | 145 | float f = 0.0f; 146 | float tst1 = 0.0f; 147 | float eps = pow(2.0f,-52.0f); 148 | for (int l = 0; l < SIZE; l++) { 149 | 150 | // Find small subdiagonal element 151 | 152 | tst1 = MAX(tst1,fabs(d[l]) + fabs(e[l])); 153 | int m = l; 154 | while (m < SIZE) { 155 | if (fabs(e[m]) <= eps*tst1) { 156 | break; 157 | } 158 | m++; 159 | } 160 | 161 | // If m == l, d[l] is an eigenvalue, 162 | // otherwise, iterate. 163 | 164 | if (m > l) { 165 | int iter = 0; 166 | do { 167 | iter = iter + 1; // (Could check iteration count here.) 168 | 169 | // Compute implicit shift 170 | 171 | float g = d[l]; 172 | float p = (d[l+1] - g) / (2.0f * e[l]); 173 | float r = hypot2(p,1.0f); 174 | if (p < 0) { 175 | r = -r; 176 | } 177 | d[l] = e[l] / (p + r); 178 | d[l+1] = e[l] * (p + r); 179 | float dl1 = d[l+1]; 180 | float h = g - d[l]; 181 | for (int i = l+2; i < SIZE; i++) { 182 | d[i] -= h; 183 | } 184 | f = f + h; 185 | 186 | // Implicit QL transformation. 187 | 188 | p = d[m]; 189 | float c = 1.0f; 190 | float c2 = c; 191 | float c3 = c; 192 | float el1 = e[l+1]; 193 | float s = 0.0f; 194 | float s2 = 0.0f; 195 | for (int i = m-1; i >= l; i--) { 196 | c3 = c2; 197 | c2 = c; 198 | s2 = s; 199 | g = c * e[i]; 200 | h = c * p; 201 | r = hypot2(p,e[i]); 202 | e[i+1] = s * r; 203 | s = e[i] / r; 204 | c = p / r; 205 | p = c * d[i] - s * g; 206 | d[i+1] = h + s * (c * g + s * d[i]); 207 | 208 | // Accumulate transformation. 209 | 210 | for (int k = 0; k < SIZE; k++) { 211 | h = V[k][i+1]; 212 | V[k][i+1] = s * V[k][i] + c * h; 213 | V[k][i] = c * V[k][i] - s * h; 214 | } 215 | } 216 | p = -s * s2 * c3 * el1 * e[l] / dl1; 217 | e[l] = s * p; 218 | d[l] = c * p; 219 | 220 | // Check for convergence. 221 | 222 | } while (fabs(e[l]) > eps*tst1); 223 | } 224 | d[l] = d[l] + f; 225 | e[l] = 0.0f; 226 | } 227 | 228 | // Sort eigenvalues and corresponding vectors. 229 | 230 | for (int i = 0; i < SIZE-1; i++) { 231 | int k = i; 232 | float p = d[i]; 233 | for (int j = i+1; j < SIZE; j++) { 234 | if (fabs(d[j]) < fabs(p)) { 235 | k = j; 236 | p = d[j]; 237 | } 238 | } 239 | if (k != i) { 240 | d[k] = d[i]; 241 | d[i] = p; 242 | for (int j = 0; j < SIZE; j++) { 243 | p = V[j][i]; 244 | V[j][i] = V[j][k]; 245 | V[j][k] = p; 246 | } 247 | } 248 | } 249 | } 250 | 251 | void eigen_decomposition(float A[SIZE][SIZE], float V[SIZE][SIZE], float d[SIZE]) { 252 | float e[SIZE]; 253 | for (int i = 0; i < SIZE; i++) { 254 | for (int j = 0; j < SIZE; j++) { 255 | V[i][j] = A[i][j]; 256 | } 257 | } 258 | tred2(V, d, e); 259 | tql2(V, d, e); 260 | } 261 | 262 | 263 | #define POS(pos) pos.x+pos.y*size.x+pos.z*size.x*size.y 264 | SIPL::float3 gradient(TubeSegmentation &TS, SIPL::int3 pos, int volumeComponent, int dimensions, int3 size) { 265 | float * Fx = TS.Fx; 266 | float * Fy = TS.Fy; 267 | float * Fz = TS.Fz; 268 | float f100, f_100, f010, f0_10, f001, f00_1; 269 | SIPL::int3 npos = pos; 270 | switch(volumeComponent) { 271 | case 0: 272 | 273 | npos.x +=1; 274 | f100 = Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 275 | npos.x -=2; 276 | f_100 = Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 277 | if(dimensions > 1) { 278 | npos = pos; 279 | npos.y += 1; 280 | f010 = Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 281 | npos.y -= 2; 282 | f0_10 = Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 283 | } 284 | if(dimensions > 2) { 285 | npos = pos; 286 | npos.z += 1; 287 | f001 = Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 288 | npos.z -= 2; 289 | f00_1 =Fx[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 290 | } 291 | break; 292 | case 1: 293 | 294 | npos.x +=1; 295 | f100 = Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 296 | npos.x -=2; 297 | f_100 = Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 298 | if(dimensions > 1) { 299 | npos = pos; 300 | npos.y += 1; 301 | f010 = Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 302 | npos.y -= 2; 303 | f0_10 = Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 304 | } 305 | if(dimensions > 2) { 306 | npos = pos; 307 | npos.z += 1; 308 | f001 = Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 309 | npos.z -= 2; 310 | f00_1 =Fy[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 311 | } 312 | break; 313 | case 2: 314 | 315 | npos.x +=1; 316 | f100 = Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 317 | npos.x -=2; 318 | f_100 = Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 319 | if(dimensions > 1) { 320 | npos = pos; 321 | npos.y += 1; 322 | f010 = Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 323 | npos.y -= 2; 324 | f0_10 = Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 325 | } 326 | if(dimensions > 2) { 327 | npos = pos; 328 | npos.z += 1; 329 | f001 = Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 330 | npos.z -= 2; 331 | f00_1 =Fz[POS(npos)]/sqrt(Fx[POS(npos)]*Fx[POS(npos)]+Fy[POS(npos)]*Fy[POS(npos)]+Fz[POS(npos)]*Fz[POS(npos)]); 332 | } 333 | break; 334 | } 335 | 336 | float3 grad(0.5f*(f100-f_100), 0.5f*(f010-f0_10), 0.5f*(f001-f00_1)); 337 | 338 | 339 | return grad; 340 | } 341 | 342 | 343 | float3 getTubeDirection(TubeSegmentation &T, int3 pos, int3 size) { 344 | 345 | // Do gradient on Fx, Fy and Fz and normalization 346 | float3 Fx = gradient(T, pos,0,1,size); 347 | float3 Fy = gradient(T, pos,1,2,size); 348 | float3 Fz = gradient(T, pos,2,3,size); 349 | 350 | float Hessian[3][3] = { 351 | {Fx.x, Fy.x, Fz.x}, 352 | {Fy.x, Fy.y, Fz.y}, 353 | {Fz.x, Fz.y, Fz.z} 354 | }; 355 | float eigenValues[3]; 356 | float eigenVectors[3][3]; 357 | eigen_decomposition(Hessian, eigenVectors, eigenValues); 358 | float3 e1(eigenVectors[0][0], eigenVectors[1][0], eigenVectors[2][0]); 359 | return e1; 360 | } 361 | 362 | void doEigen(TubeSegmentation &T, int3 pos, int3 size, float3 * lambda, float3 * e1, float3 * e2, float3 * e3) { 363 | 364 | // Do gradient on Fx, Fy and Fz and normalization 365 | float3 Fx = gradient(T, pos,0,1,size); 366 | float3 Fy = gradient(T, pos,1,2,size); 367 | float3 Fz = gradient(T, pos,2,3,size); 368 | 369 | float Hessian[3][3] = { 370 | {Fx.x, Fy.x, Fz.x}, 371 | {Fy.x, Fy.y, Fz.y}, 372 | {Fz.x, Fz.y, Fz.z} 373 | }; 374 | float eigenValues[3]; 375 | float eigenVectors[3][3]; 376 | eigen_decomposition(Hessian, eigenVectors, eigenValues); 377 | e1->x = eigenVectors[0][0]; 378 | e1->y = eigenVectors[1][0]; 379 | e1->z = eigenVectors[2][0]; 380 | e2->x = eigenVectors[0][1]; 381 | e2->y = eigenVectors[1][1]; 382 | e2->z = eigenVectors[2][1]; 383 | e3->x = eigenVectors[0][2]; 384 | e3->y = eigenVectors[1][2]; 385 | e3->z = eigenVectors[2][2]; 386 | lambda->x = eigenValues[0]; 387 | lambda->y = eigenValues[1]; 388 | lambda->z = eigenValues[2]; 389 | } 390 | -------------------------------------------------------------------------------- /eigenanalysisOfHessian.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EIGEN_HESSIAN_H 2 | #define EIGEN_HESSIAN_H 3 | #include "SIPL/Types.hpp" 4 | #include "tube-segmentation.hpp" 5 | using namespace SIPL; 6 | 7 | void doEigen(TubeSegmentation &T, int3 pos, int3 size, float3 * lambda, float3 * e1, float3 * e2, float3 * e3); 8 | 9 | float3 getTubeDirection(TubeSegmentation &T, int3 pos, int3 size); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /globalCenterlineExtraction.cpp: -------------------------------------------------------------------------------- 1 | #include "globalCenterlineExtraction.hpp" 2 | #include "eigenanalysisOfHessian.hpp" 3 | #include "SIPL/Types.hpp" 4 | #ifdef CPP11 5 | #include 6 | using std::unordered_set; 7 | #else 8 | #include 9 | using boost::unordered_set; 10 | #endif 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace SIPL; 16 | 17 | #define MAX(a,b) a > b ? a : b 18 | 19 | #define LPOS(a,b,c) (a)+(b)*(size.x)+(c)*(size.x*size.y) 20 | #define POS(pos) pos.x+pos.y*size.x+pos.z*size.x*size.y 21 | #define M(a,b,c) 1-sqrt(pow(T.Fx[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fy[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fz[a+b*size.x+c*size.x*size.y],2.0f)) 22 | #define SQR_MAG(pos) sqrt(pow(T.Fx[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fy[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fz[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 23 | #define SQR_MAG_SMALL(pos) sqrt(pow(T.FxSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FySmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FzSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 24 | 25 | class CrossSectionCompare { 26 | private: 27 | float * dist; 28 | public: 29 | CrossSectionCompare(float * dist) { this->dist = dist; }; 30 | bool operator() (const CrossSection * a, const CrossSection * b) { 31 | return dist[a->index] > dist[b->index]; 32 | }; 33 | }; 34 | 35 | std::vector createGraph(TubeSegmentation &T, SIPL::int3 size) { 36 | // Create vector 37 | std::vector sections; 38 | float threshold = 0.5f; 39 | 40 | // Go through TS.TDF and add all with TDF above threshold 41 | int counter = 0; 42 | float thetaLimit = 0.5; 43 | for(int z = 1; z < size.z-1; z++) { 44 | for(int y = 1; y < size.y-1; y++) { 45 | for(int x = 1; x < size.x-1; x++) { 46 | int3 pos(x,y,z); 47 | float tdf = T.TDF[POS(pos)]; 48 | if(tdf > threshold) { 49 | int maxD = std::min(std::max((int)round(T.radius[POS(pos)]), 1), 5); 50 | //std::cout << SQR_MAG(pos) << " " << SQR_MAG_SMALL(pos) << std::endl; 51 | //std::cout << "radius" << TS.radius[POS(pos)] << std::endl; 52 | //std::cout << "maxD "<< maxD < TS.TDF[POS(pos)]) { 76 | if(SQR_MAG_SMALL(n) < SQR_MAG_SMALL(pos)) { 77 | invalid = true; 78 | break; 79 | } 80 | } else { 81 | */ 82 | if(SQR_MAG(n) < SQR_MAG(pos)) { 83 | //if(TS.TDF[POS(n)] > TS.TDF[POS(pos)]) { 84 | invalid = true; 85 | break; 86 | } 87 | //} 88 | } 89 | }}} 90 | if(!invalid) { 91 | CrossSection * cs = new CrossSection; 92 | cs->pos = pos; 93 | cs->TDF = tdf; 94 | cs->label = -1;//counter; 95 | cs->direction = e1; 96 | counter++; 97 | sections.push_back(cs); 98 | } 99 | } 100 | }}} 101 | 102 | 103 | // For each cross section c_i 104 | for(int i = 0; i < sections.size(); i++) { 105 | CrossSection * c_i = sections[i]; 106 | // For each cross section c_j 107 | for(int j = 0; j < i; j++) { 108 | CrossSection * c_j = sections[j]; 109 | // If all criterias are ok: Add c_j as neighbor to c_i 110 | if(c_i->pos.distance(c_j->pos) < 4 && !(c_i->pos == c_j->pos)) { 111 | float3 e1_i = c_i->direction; 112 | float3 e1_j = c_j->direction; 113 | int3 cint = c_i->pos - c_j->pos; 114 | float3 c = cint.normalize(); 115 | 116 | if(acos((double)fabs(e1_i.dot(e1_j))) > 1.05) // 60 degrees 117 | continue; 118 | 119 | if(acos((double)fabs(e1_i.dot(c))) > 1.05) 120 | continue; 121 | 122 | if(acos((double)fabs(e1_j.dot(c))) > 1.05) 123 | continue; 124 | 125 | int distance = ceil(c_i->pos.distance(c_j->pos)); 126 | float3 direction(c_j->pos.x-c_i->pos.x,c_j->pos.y-c_i->pos.y,c_j->pos.z-c_i->pos.z); 127 | bool invalid = false; 128 | for(int i = 0; i < distance; i++) { 129 | float frac = (float)i/distance; 130 | float3 n = c_i->pos + frac*direction; 131 | int3 in(round(n.x),round(n.y),round(n.z)); 132 | //float3 e1 = getTubeDirection(TS, in, size); 133 | //cost += (1-fabs(a->direction.dot(e1)))+(1-fabs(b->direction.dot(e1))); 134 | /* 135 | if(T.intensity[POS(in)] > 0.5f) { 136 | invalid = true; 137 | break; 138 | } 139 | */ 140 | } 141 | //if(invalid) 142 | // continue; 143 | 144 | 145 | c_i->neighbors.push_back(c_j); 146 | c_j->neighbors.push_back(c_i); 147 | //sectionPairs.push_back(c_i); 148 | } 149 | // If no pair is found, dont add it 150 | } 151 | } 152 | 153 | std::vector sectionPairs; 154 | for(int i = 0; i < sections.size(); i++) { 155 | CrossSection * c_i = sections[i]; 156 | if(c_i->neighbors.size()>0) { 157 | sectionPairs.push_back(c_i); 158 | } 159 | } 160 | 161 | return sectionPairs; 162 | } 163 | 164 | 165 | bool segmentCompare(Segment * a, Segment * b) { 166 | return a->benefit > b->benefit; 167 | } 168 | 169 | bool segmentInSegmentation(Segment * s, unordered_set &segmentation, int3 size) { 170 | bool in = false; 171 | for(int i = 0; i < s->sections.size(); i++) { 172 | CrossSection * c = s->sections[i]; 173 | if(segmentation.find(POS(c->pos)) != segmentation.end()) { 174 | in = true; 175 | break; 176 | } 177 | } 178 | return in; 179 | } 180 | 181 | float calculateBenefit(CrossSection * a, CrossSection * b, TubeSegmentation &TS, int3 size) { 182 | float benefit = 0.0f; 183 | int distance = ceil(a->pos.distance(b->pos)); 184 | float3 direction(b->pos.x-a->pos.x,b->pos.y-a->pos.y,b->pos.z-a->pos.z); 185 | for(int i = 0; i < distance; i++) { 186 | float frac = (float)i/distance; 187 | float3 n = a->pos + frac*direction; 188 | int3 in(round(n.x),round(n.y),round(n.z)); 189 | benefit += (TS.TDF[POS(in)]); 190 | } 191 | 192 | return benefit; 193 | } 194 | 195 | void inverseGradientRegionGrowing(Segment * s, TubeSegmentation &TS, unordered_set &segmentation, int3 size) { 196 | std::vector centerpoints; 197 | for(int c = 0; c < s->sections.size()-1; c++) { 198 | CrossSection * a = s->sections[c]; 199 | CrossSection * b = s->sections[c+1]; 200 | int distance = ceil(a->pos.distance(b->pos)); 201 | float3 direction(b->pos.x-a->pos.x,b->pos.y-a->pos.y,b->pos.z-a->pos.z); 202 | for(int i = 0; i < distance; i++) { 203 | float frac = (float)i/distance; 204 | float3 n = a->pos + frac*direction; 205 | int3 in(round(n.x),round(n.y),round(n.z)); 206 | centerpoints.push_back(in); 207 | } 208 | segmentation.insert(POS(a->pos));//test 209 | segmentation.insert(POS(b->pos));//test 210 | } 211 | 212 | // Dilate the centerline 213 | std::vector dilatedCenterline; 214 | for(int i = 0; i < centerpoints.size(); i++) { 215 | int3 pos = centerpoints[i]; 216 | for(int a = -1; a < 2; a++) { 217 | for(int b = -1; b < 2; b++) { 218 | for(int c = -1; c < 2; c++) { 219 | int3 n = pos + int3(a,b,c); 220 | if(inBounds(n, size) && segmentation.find(POS(n)) == segmentation.end()) { 221 | segmentation.insert(POS(n)); 222 | dilatedCenterline.push_back(n); 223 | } 224 | }}} 225 | } 226 | /* 227 | 228 | std::queue queue; 229 | for(int3 pos : dilatedCenterline) { 230 | for(int a = -1; a < 2; a++) { 231 | for(int b = -1; b < 2; b++) { 232 | for(int c = -1; c < 2; c++) { 233 | int3 n = pos + int3(a,b,c); 234 | if(inBounds(n, size) && segmentation.find(POS(n)) == segmentation.end()) { 235 | queue.push(n); 236 | } 237 | }}} 238 | } 239 | 240 | while(!queue.empty()) { 241 | int3 X = queue.front(); 242 | float FNXw = SQR_MAG(X); 243 | queue.pop(); 244 | for(int a = -1; a < 2; a++) { 245 | for(int b = -1; b < 2; b++) { 246 | for(int c = -1; c < 2; c++) { 247 | if(a == 0 && b == 0 && c == 0) 248 | continue; 249 | 250 | int3 Y = X + int3(a,b,c); 251 | if(inBounds(Y, size) && segmentation.find(POS(Y)) == segmentation.end()) { 252 | 253 | float3 FNY; 254 | FNY.x = TS.Fx[POS(Y)]; 255 | FNY.y = TS.Fy[POS(Y)]; 256 | FNY.z = TS.Fz[POS(Y)]; 257 | float FNYw = FNY.length(); 258 | FNY = FNY.normalize(); 259 | if(FNYw > FNXw || FNXw < 0.1f) { 260 | 261 | int3 Z; 262 | float maxDotProduct = -2.0f; 263 | for(int a2 = -1; a2 < 2; a2++) { 264 | for(int b2 = -1; b2 < 2; b2++) { 265 | for(int c2 = -1; c2 < 2; c2++) { 266 | if(a2 == 0 && b2 == 0 && c2 == 0) 267 | continue; 268 | int3 Zc; 269 | Zc.x = Y.x+a2; 270 | Zc.y = Y.y+b2; 271 | Zc.z = Y.z+c2; 272 | float3 YZ; 273 | YZ.x = Zc.x-Y.x; 274 | YZ.y = Zc.y-Y.y; 275 | YZ.z = Zc.z-Y.z; 276 | YZ = YZ.normalize(); 277 | if(FNY.dot(YZ) > maxDotProduct) { 278 | maxDotProduct = FNY.dot(YZ); 279 | Z = Zc; 280 | } 281 | }}} 282 | 283 | if(Z.x == X.x && Z.y == X.y && Z.z == X.z) { 284 | segmentation.insert(POS(X)); 285 | queue.push(Y); 286 | } 287 | } 288 | } 289 | }}} 290 | } 291 | */ 292 | 293 | } 294 | 295 | std::vector createSegments(OpenCL &ocl, TubeSegmentation &TS, std::vector &crossSections, SIPL::int3 size) { 296 | // Create segment vector 297 | std::vector segments; 298 | 299 | // Do a graph component labeling 300 | unordered_set visited; 301 | int labelCounter = 0; 302 | std::vector > labels; 303 | for(int i = 0; i < crossSections.size(); i++) { 304 | CrossSection * c = crossSections[i]; 305 | // Do a bfs on c 306 | // Check to see if point has been processed before doing a BFS 307 | if(visited.find(c->label) != visited.end()) 308 | continue; 309 | 310 | c->label = labelCounter; 311 | labelCounter++; 312 | std::vector list; 313 | 314 | std::stack stack; 315 | stack.push(c); 316 | while(!stack.empty()) { 317 | CrossSection * current = stack.top(); 318 | stack.pop(); 319 | // Check label of neighbors to see if they have been added 320 | if(current->label != c->label || c->pos == current->pos) { 321 | list.push_back(current); 322 | // Change label of neighbors if not 323 | current->label = c->label; 324 | // Add neighbors to stack 325 | 326 | for(int j = 0; j < current->neighbors.size(); j++) { 327 | CrossSection * n = current->neighbors[j]; 328 | if(n->label != c->label) 329 | stack.push(n); 330 | } 331 | } 332 | } 333 | visited.insert(c->label); 334 | labels.push_back(list); 335 | } 336 | 337 | std::cout << "finished graph component labeling" << std::endl; 338 | 339 | // Do a floyd warshall all pairs shortest path 340 | int totalSize = crossSections.size(); 341 | std::cout << "number of cross sections is " << totalSize << std::endl; 342 | 343 | // For each label 344 | for(int i = 0; i < labels.size(); i++) { 345 | std::vector list = labels[i]; 346 | // Do floyd warshall on all pairs 347 | int totalSize = list.size(); 348 | float * dist = new float[totalSize*totalSize]; 349 | int * pred = new int[totalSize*totalSize]; 350 | 351 | for(int u = 0; u < totalSize; u++) { 352 | CrossSection * U = list[u]; 353 | U->index = u; 354 | } 355 | #define DPOS(U, V) V+U*totalSize 356 | // For each cross section U 357 | for(int u = 0; u < totalSize; u++) { 358 | CrossSection * U = list[u]; 359 | // For each cross section V 360 | for(int v = 0; v < totalSize; v++) { 361 | dist[DPOS(u,v)] = 99999999; 362 | pred[DPOS(u,v)] = -1; 363 | } 364 | dist[DPOS(U->index,U->index)] = 0; 365 | for(int j = 0; j < U->neighbors.size(); j++) { 366 | CrossSection * V = U->neighbors[j]; 367 | // TODO calculate more advanced weight 368 | dist[DPOS(U->index,V->index)] = ceil(U->pos.distance(V->pos)) - calculateBenefit(U, V, TS, size); //(1-V->TDF); 369 | pred[DPOS(U->index,V->index)] = U->index; 370 | } 371 | } 372 | for(int t = 0; t < totalSize; t++) { 373 | //CrossSection * T = crossSections[t]; 374 | //std::cout << "processing t=" << t << std::endl; 375 | // For each cross section U 376 | for(int u = 0; u < totalSize; u++) { 377 | //CrossSection * U = crossSections[u]; 378 | // For each cross section V 379 | for(int v = 0; v < totalSize; v++) { 380 | //CrossSection * V = crossSections[v]; 381 | float newLength = dist[DPOS(u, t)] + dist[DPOS(t,v)]; 382 | if(newLength < dist[DPOS(u,v)]) { 383 | dist[DPOS(u,v)] = newLength; 384 | pred[DPOS(u,v)] = pred[DPOS(t,v)]; 385 | } 386 | } 387 | } 388 | } 389 | 390 | for(int s = 0; s < list.size(); s++) { // Source 391 | CrossSection * S = list[s]; 392 | for(int t = 0; t < list.size(); t++) { // Target 393 | CrossSection * T = list[t]; 394 | if(S->label == T->label && S->index != T->index) { 395 | Segment * segment = new Segment; 396 | // add all cross sections in segment 397 | float benefit = 0.0f; 398 | segment->sections.push_back(T); 399 | int current = T->index; 400 | while(current != S->index) { 401 | CrossSection * C = list[current]; 402 | segment->sections.push_back(C); 403 | current = pred[DPOS(S->index,current)];// get predecessor 404 | benefit += calculateBenefit(C, list[current], TS, size); 405 | } 406 | segment->sections.push_back(list[current]); 407 | segment->benefit = benefit; 408 | segments.push_back(segment); 409 | } 410 | } 411 | } 412 | 413 | delete[] dist; 414 | delete[] pred; 415 | } 416 | std::cout << "finished performing floyd warshall" << std::endl; 417 | 418 | std::cout << "finished creating segments" << std::endl; 419 | std::cout << "total number of segments is " << segments.size() << std::endl; 420 | 421 | 422 | // Sort the segment vector on benefit 423 | std::sort(segments.begin(), segments.end(), segmentCompare); 424 | unordered_set segmentation; 425 | 426 | // Go through sorted vector and do a region growing 427 | std::vector filteredSegments; 428 | int counter = 0; 429 | for(int i = 0; i < segments.size(); i++) { 430 | Segment * s = segments[i]; 431 | if(!segmentInSegmentation(s, segmentation, size)) { 432 | //std::cout << "adding segment with benefit: " << s->benefit << std::endl; 433 | // Do region growing and Add all segmented voxels to a set 434 | inverseGradientRegionGrowing(s, TS, segmentation, size); 435 | filteredSegments.push_back(s); 436 | s->index = counter; 437 | counter++; 438 | } 439 | } 440 | 441 | std::cout << "total number of segments after remove overlapping segments " << filteredSegments.size() << std::endl; 442 | 443 | return filteredSegments; 444 | } 445 | 446 | int selectRoot(std::vector segments) { 447 | int root = 0; 448 | for(int i = 1; i < segments.size(); i++) { 449 | if(segments[i]->benefit > segments[root]->benefit) 450 | root = i; 451 | } 452 | return root; 453 | } 454 | 455 | void DFS(Segment * current, int * ordering, int &counter, unordered_set &visited) { 456 | if(visited.find(current->index) != visited.end()) 457 | return; 458 | ordering[counter] = current->index; 459 | //std::cout << counter << ": " << current->index << std::endl; 460 | counter++; 461 | for(int i = 0; i < current->connections.size(); i++) { 462 | Connection * edge = current->connections[i]; 463 | DFS(edge->target, ordering, counter, visited); 464 | } 465 | visited.insert(current->index); 466 | } 467 | 468 | int * createDepthFirstOrdering(std::vector segments, int root, int &Ns) { 469 | int * ordering = new int[segments.size()]; 470 | int counter = 0; 471 | 472 | // Give imdexes to segments 473 | for(int i = 0; i < segments.size(); i++) { 474 | segments[i]->index = i; 475 | } 476 | 477 | unordered_set visited; 478 | 479 | DFS(segments[root], ordering, counter, visited); 480 | 481 | Ns = counter; 482 | int * reversedOrdering = new int[Ns]; 483 | for(int i = 0; i < Ns; i++) { 484 | reversedOrdering[i] = ordering[Ns-i-1]; 485 | } 486 | 487 | delete[] ordering; 488 | return reversedOrdering; 489 | } 490 | 491 | class ConnectionComparator { 492 | public: 493 | bool operator()(Connection * a, Connection *b) const { 494 | return a->cost > b->cost; 495 | } 496 | }; 497 | 498 | std::vector minimumSpanningTree(Segment * root, int3 size) { 499 | // Need a priority queue on Connection objects based on the cost 500 | std::priority_queue, ConnectionComparator> queue; 501 | std::vector result; 502 | unordered_set visited; 503 | result.push_back(root); 504 | visited.insert(root->index); 505 | 506 | // Add all connections of the root to the queue 507 | for(int i = 0; i < root->connections.size(); i++) { 508 | Connection * c = root->connections[i]; 509 | queue.push(c); 510 | } 511 | // Remove connections from root 512 | root->connections = std::vector(); 513 | 514 | while(!queue.empty()) { 515 | // Select minimum connection 516 | // Check if target is already added 517 | // if not, add all of its connection to the queue 518 | // add this connection to the source 519 | // Add target segment and clear its connections 520 | // Also add cost to the segment object 521 | Connection * c = queue.top(); 522 | //std::cout << c->cost << std::endl; 523 | queue.pop(); 524 | if(visited.find(c->target->index) != visited.end()) 525 | continue; 526 | 527 | for(int i = 0; i < c->target->connections.size(); i++) { 528 | Connection * cn = c->target->connections[i]; 529 | if(visited.find(cn->target->index) == visited.end()) 530 | queue.push(cn); 531 | } 532 | 533 | c->source->connections.push_back(c); 534 | // c->target->connections.clear(); doest his delete the objects? 535 | c->target->connections = std::vector(); 536 | c->target->cost = c->cost; 537 | result.push_back(c->target); 538 | visited.insert(c->target->index); 539 | } 540 | 541 | return result; 542 | } 543 | 544 | std::vector findOptimalSubtree(std::vector segments, int * depthFirstOrdering, int Ns) { 545 | 546 | float * score = new float[Ns](); 547 | float r = 2.0; 548 | 549 | // Stage 1 bottom up 550 | for(int j = 0; j < Ns; j++) { 551 | int mj = depthFirstOrdering[j]; 552 | score[mj] = segments[mj]->benefit - r * segments[mj]->cost; 553 | /*std::cout << "cross sections: " << segments[mj]->sections.size() << " benefit: " 554 | << segments[mj]->benefit << " cost: " << segments[mj]->cost << 555 | " children: " << segments[mj]->connections.size() << std::endl;*/ 556 | // For all children of mj 557 | for(int n = 0; n < segments[mj]->connections.size(); n++) { 558 | Connection * c = segments[mj]->connections[n]; 559 | int k = c->target->index; //child 560 | if(score[k] >= 0) 561 | score[mj] += score[k]; 562 | } 563 | } 564 | 565 | // Stage 2 top down 566 | bool * v = new bool[Ns]; 567 | for(int i = 1; i < Ns; i++) 568 | v[i] = false; 569 | v[0] = true; 570 | 571 | for(int j = Ns-1; j >= 0; j--) { 572 | int mj = depthFirstOrdering[j]; 573 | if(v[mj]) { 574 | // For all children of mj 575 | for(int n = 0; n < segments[mj]->connections.size(); n++) { 576 | Connection * c = segments[mj]->connections[n]; 577 | int k = c->target->index; //child 578 | if(score[k] >= 0) 579 | v[k] = true; 580 | } 581 | } 582 | } 583 | 584 | delete[] score; 585 | 586 | std::vector finalSegments; 587 | for(int i = 0; i < Ns; i++) { 588 | if(v[i]) { 589 | finalSegments.push_back(segments[i]); 590 | 591 | // for all children, check if they are true in v, if not remove connections 592 | std::vector connections; 593 | for(int n = 0; n < segments[i]->connections.size(); n++) { 594 | Connection * c = segments[i]->connections[n]; 595 | int k = c->target->index; //child 596 | if(v[k]) { 597 | // keep connection 598 | connections.push_back(c); 599 | } else { 600 | delete c; 601 | } 602 | } 603 | segments[i]->connections = connections; 604 | } 605 | } 606 | delete[] v; 607 | return finalSegments; 608 | } 609 | 610 | float calculateConnectionCost(CrossSection * a, CrossSection * b, TubeSegmentation &TS, int3 size) { 611 | float cost = 0.0f; 612 | int distance = ceil(a->pos.distance(b->pos)); 613 | float3 direction(b->pos.x-a->pos.x,b->pos.y-a->pos.y,b->pos.z-a->pos.z); 614 | float maxIntensity = -1.0f; 615 | for(int i = 0; i < distance; i++) { 616 | float frac = (float)i/distance; 617 | float3 n = a->pos + frac*direction; 618 | int3 in(round(n.x),round(n.y),round(n.z)); 619 | cost += /*SQR_MAG(in) +*/ (1.0f-TS.TDF[POS(in)]); 620 | //float3 e1 = getTubeDirection(TS, in, size); 621 | //cost += (1-fabs(a->direction.dot(e1)))+(1-fabs(b->direction.dot(e1))); 622 | if(TS.intensity[POS(in)] > maxIntensity) { 623 | maxIntensity = TS.intensity[POS(in)]; 624 | } 625 | } 626 | /* 627 | if(maxIntensity > 0.2 && maxIntensity < 0.3) { 628 | cost = cost*2; 629 | } 630 | if(maxIntensity >= 0.3 && maxIntensity < 0.5) { 631 | cost = cost*4; 632 | } 633 | if(maxIntensity >= 0.5) { 634 | cost = cost*8; 635 | } 636 | */ 637 | return cost; 638 | } 639 | 640 | void createConnections(TubeSegmentation &TS, std::vector segments, int3 size) { 641 | // For all pairs of segments 642 | for(int k = 0; k < segments.size(); k++) { 643 | Segment * s_k = segments[k]; 644 | for(int l = 0; l < k; l++) { 645 | Segment * s_l = segments[l]; 646 | // For each C_k, cross sections in S_k, calculate costs and select the one with least cost 647 | float bestCost = 999999999.0f; 648 | CrossSection * c_k_best, * c_l_best; 649 | bool found = false; 650 | for(int i = 0; i < s_k->sections.size(); i++){ 651 | CrossSection * c_k = s_k->sections[i]; 652 | for(int j = 0; j < s_l->sections.size(); j++){ 653 | CrossSection * c_l = s_l->sections[j]; 654 | if(c_k->pos.distance(c_l->pos) > 20) 655 | continue; 656 | 657 | float3 c(c_k->pos.x-c_l->pos.x, c_k->pos.y-c_l->pos.y,c_k->pos.z-c_l->pos.z); 658 | c = c.normalize(); 659 | if(acos(fabs(c_k->direction.dot(c))) > 1.05f) 660 | continue; 661 | if(acos(fabs(c_l->direction.dot(c))) > 1.05f) 662 | continue; 663 | 664 | /* 665 | float rk = TS.radius[POS(c_k->pos)]; 666 | float rl = TS.radius[POS(c_l->pos)]; 667 | 668 | if(rk > 2 || rl > 2) { 669 | if(std::max(rk,rl) / std::min(rk,rl) >= 2) 670 | continue; 671 | } 672 | */ 673 | 674 | float cost = calculateConnectionCost(c_k, c_l, TS, size); 675 | if(cost < bestCost) { 676 | bestCost = cost; 677 | c_k_best = c_k; 678 | c_l_best = c_l; 679 | found = true; 680 | } 681 | } 682 | } 683 | 684 | 685 | // See if they are allowed to connect 686 | if(found) { 687 | /*if(bestCost < 2) { 688 | std::cout << bestCost << std::endl; 689 | std::cout << "labels: " << c_k_best->label << " " << c_l_best->label << std::endl; 690 | std::cout << "distance: " << c_k_best->pos.distance(c_l_best->pos) << std::endl; 691 | }*/ 692 | // If so, create connection object and add to segemnt 693 | Connection * c = new Connection; 694 | c->cost = bestCost; 695 | c->source = s_k; 696 | c->source_section = c_k_best; 697 | c->target = s_l; 698 | c->target_section = c_l_best; 699 | s_k->connections.push_back(c); 700 | Connection * c2 = new Connection; 701 | c2->cost = bestCost; 702 | c2->source = s_l; 703 | c2->source_section = c_l_best; 704 | c2->target = s_k; 705 | c2->target_section = c_k_best; 706 | s_l->connections.push_back(c2); 707 | } 708 | } 709 | } 710 | } 711 | 712 | -------------------------------------------------------------------------------- /globalCenterlineExtraction.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_CENTERLINE_EXTRACTION_H 2 | #define GLOBAL_CENTERLINE_EXTRACTION_H 3 | #include "SIPL/Types.hpp" 4 | #include 5 | #include "tube-segmentation.hpp" 6 | using namespace SIPL; 7 | 8 | class CrossSection { 9 | public: 10 | int3 pos; 11 | float TDF; 12 | std::vector neighbors; 13 | int label; 14 | int index; 15 | float3 direction; 16 | }; 17 | 18 | class Connection; 19 | 20 | class Segment { 21 | public: 22 | std::vector sections; 23 | std::vector connections; 24 | float benefit; 25 | float cost; 26 | int index; 27 | }; 28 | 29 | class Connection { 30 | public: 31 | Segment * source; 32 | Segment * target; 33 | CrossSection * source_section; 34 | CrossSection * target_section; 35 | float cost; 36 | }; 37 | 38 | std::vector createGraph(TubeSegmentation &T, SIPL::int3 size); 39 | 40 | std::vector createSegments(OpenCL &ocl, TubeSegmentation &TS, std::vector &crossSections, SIPL::int3 size); 41 | int selectRoot(std::vector segments); 42 | int * createDepthFirstOrdering(std::vector segments, int root, int &Ns); 43 | 44 | std::vector minimumSpanningTree(Segment * root, int3 size); 45 | std::vector findOptimalSubtree(std::vector segments, int * depthFirstOrdering, int Ns); 46 | void createConnections(TubeSegmentation &TS, std::vector segments, int3 size); 47 | #endif 48 | -------------------------------------------------------------------------------- /gradientVectorFlow.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GVF_H 2 | #define GVF_H 3 | #include "commons.hpp" 4 | #include "SIPL/Types.hpp" 5 | #include "parameters.hpp" 6 | using namespace cl; 7 | 8 | Image3D runGVF(OpenCL &ocl, Image3D * vectorField, paramList ¶meters, SIPL::int3 &size, bool useLessMemory); 9 | 10 | Image3D runFMGGVF(OpenCL &ocl, Image3D *vectorField, paramList ¶meters, SIPL::int3 &size); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /inputOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "inputOutput.hpp" 2 | #include 3 | #include "SIPL/Exceptions.hpp" 4 | using namespace SIPL; 5 | using namespace cl; 6 | 7 | template 8 | void writeToRaw(T * voxels, std::string filename, int SIZE_X, int SIZE_Y, int SIZE_Z) { 9 | FILE * file = fopen(filename.c_str(), "wb"); 10 | fwrite(voxels, sizeof(T), SIZE_X*SIZE_Y*SIZE_Z, file); 11 | fclose(file); 12 | } 13 | 14 | void writeDataToDisk(TSFOutput * output, std::string storageDirectory, std::string name) { 15 | SIPL::int3 * size = output->getSize(); 16 | if(output->hasCenterlineVoxels()) { 17 | // Create MHD file 18 | std::ofstream file; 19 | std::string filename = storageDirectory + name + ".centerline.mhd"; 20 | file.open(filename.c_str()); 21 | file << "ObjectType = Image\n"; 22 | file << "NDims = 3\n"; 23 | file << "DimSize = " << output->getSize()->x << " " << output->getSize()->y << " " << output->getSize()->z << "\n"; 24 | file << "ElementSpacing = " << output->getSpacing().x << " " << output->getSpacing().y << " " << output->getSpacing().z << "\n"; 25 | file << "ElementType = MET_CHAR\n"; 26 | file << "ElementDataFile = " << name << ".centerline.raw\n"; 27 | file.close(); 28 | writeToRaw(output->getCenterlineVoxels(), storageDirectory + name + ".centerline.raw", size->x, size->y, size->z); 29 | } 30 | 31 | if(output->hasSegmentation()) { 32 | // Create MHD file 33 | std::ofstream file; 34 | std::string filename = storageDirectory + name + ".segmentation.mhd"; 35 | file.open(filename.c_str()); 36 | file << "ObjectType = Image\n"; 37 | file << "NDims = 3\n"; 38 | file << "DimSize = " << output->getSize()->x << " " << output->getSize()->y << " " << output->getSize()->z << "\n"; 39 | file << "ElementSpacing = " << output->getSpacing().x << " " << output->getSpacing().y << " " << output->getSpacing().z << "\n"; 40 | file << "ElementType = MET_CHAR\n"; 41 | file << "ElementDataFile = " << name << ".segmentation.raw\n"; 42 | file.close(); 43 | 44 | writeToRaw(output->getSegmentation(), storageDirectory + name + ".segmentation.raw", size->x, size->y, size->z); 45 | } 46 | } 47 | 48 | void writeToVtkFile(paramList ¶meters, std::vector vertices, std::vector edges) { 49 | // Write to file 50 | std::ofstream file; 51 | file.open(getParamStr(parameters, "centerline-vtk-file").c_str()); 52 | file << "# vtk DataFile Version 3.0\nvtk output\nASCII\n"; 53 | file << "DATASET POLYDATA\nPOINTS " << vertices.size() << " int\n"; 54 | for(int i = 0; i < vertices.size(); i++) { 55 | file << vertices[i].x << " " << vertices[i].y << " " << vertices[i].z << "\n"; 56 | } 57 | 58 | file << "\nLINES " << edges.size() << " " << edges.size()*3 << "\n"; 59 | for(int i = 0; i < edges.size(); i++) { 60 | file << "2 " << edges[i].x << " " << edges[i].y << "\n"; 61 | } 62 | 63 | file.close(); 64 | } 65 | 66 | TSFOutput::TSFOutput(oul::DeviceCriteria criteria, SIPL::int3 * size, bool TDFis16bit) { 67 | oul::OpenCLManager * manager = oul::OpenCLManager::getInstance(); 68 | //manager->setDebugMode(true); 69 | std::vector platformDevices = manager->getDevices(criteria); 70 | std::vector validDevices = manager->getDevicesForBestPlatform( 71 | criteria, platformDevices); 72 | 73 | this->context = new oul::Context(validDevices,false,false);//TODO:, false, getParamBool(parameters, "timing")); 74 | this->TDFis16bit = TDFis16bit; 75 | OpenCL * ocl = new OpenCL; 76 | ocl->context = context->getContext(); 77 | ocl->platform = context->getPlatform(); 78 | ocl->queue = context->getQueue(0); 79 | ocl->device = context->getDevice(0); 80 | this->ocl = ocl; 81 | this->size = size; 82 | hostHasCenterlineVoxels = false; 83 | hostHasSegmentation = false; 84 | hostHasTDF = false; 85 | deviceHasCenterlineVoxels = false; 86 | deviceHasSegmentation = false; 87 | deviceHasTDF = false; 88 | } 89 | 90 | oul::Context * TSFOutput::getContext() { 91 | return this->context; 92 | } 93 | 94 | TSFOutput::~TSFOutput() { 95 | if(hostHasTDF) 96 | delete[] TDF; 97 | if(hostHasSegmentation) 98 | delete[] segmentation; 99 | if(hostHasCenterlineVoxels) 100 | delete[] centerlineVoxels; 101 | if(deviceHasTDF) 102 | delete oclTDF; 103 | if(deviceHasSegmentation) 104 | delete oclSegmentation; 105 | if(deviceHasCenterlineVoxels) 106 | delete oclCenterlineVoxels; 107 | delete ocl; 108 | delete size; 109 | } 110 | 111 | void TSFOutput::setTDF(Image3D * image) { 112 | deviceHasTDF = true; 113 | oclTDF = image; 114 | } 115 | 116 | void TSFOutput::setTDF(float * data) { 117 | hostHasTDF = true; 118 | TDF = data; 119 | } 120 | 121 | void TSFOutput::setSegmentation(Image3D * image) { 122 | deviceHasSegmentation = true; 123 | oclSegmentation = image; 124 | } 125 | 126 | void TSFOutput::setSegmentation(char * data) { 127 | hostHasSegmentation = true; 128 | segmentation = data; 129 | } 130 | 131 | void TSFOutput::setCenterlineVoxels(Image3D * image) { 132 | deviceHasCenterlineVoxels = true; 133 | oclCenterlineVoxels = image; 134 | } 135 | 136 | void TSFOutput::setCenterlineVoxels(char * data) { 137 | hostHasCenterlineVoxels = true; 138 | centerlineVoxels = data; 139 | } 140 | 141 | void TSFOutput::setSize(SIPL::int3 * size) { 142 | this->size = size; 143 | } 144 | 145 | float * TSFOutput::getTDF() { 146 | if(hostHasTDF) { 147 | return TDF; 148 | } else if(deviceHasTDF) { 149 | // Transfer data from device to host 150 | cl::size_t<3> origin; 151 | origin[0] = 0; 152 | origin[1] = 0; 153 | origin[2] = 0; 154 | cl::size_t<3> region; 155 | region[0] = size->x; 156 | region[1] = size->y; 157 | region[2] = size->z; 158 | int totalSize = size->x*size->y*size->z; 159 | TDF = new float[totalSize]; 160 | if(TDFis16bit) { 161 | unsigned short * tempTDF = new unsigned short[totalSize]; 162 | ocl->queue.enqueueReadImage(*oclTDF,CL_TRUE, origin, region, 0, 0, tempTDF); 163 | for(int i = 0; i < totalSize;i++) { 164 | TDF[i] = (float)tempTDF[i] / 65535.0f; 165 | } 166 | delete[] tempTDF; 167 | } else { 168 | ocl->queue.enqueueReadImage(*oclTDF,CL_TRUE, origin, region, 0, 0, TDF); 169 | } 170 | hostHasTDF = true; 171 | return TDF; 172 | } else { 173 | throw SIPL::SIPLException("Trying to fetch non existing data from TSFOutput", __LINE__, __FILE__); 174 | } 175 | } 176 | 177 | char * TSFOutput::getSegmentation() { 178 | if(hostHasSegmentation) { 179 | return segmentation; 180 | } else if(deviceHasSegmentation) { 181 | // Transfer data from device to host 182 | cl::size_t<3> origin; 183 | origin[0] = 0; 184 | origin[1] = 0; 185 | origin[2] = 0; 186 | cl::size_t<3> region; 187 | region[0] = size->x; 188 | region[1] = size->y; 189 | region[2] = size->z; 190 | segmentation = new char[size->x*size->y*size->z]; 191 | ocl->queue.enqueueReadImage(*oclSegmentation,CL_TRUE, origin, region, 0, 0, segmentation); 192 | hostHasSegmentation = true; 193 | return segmentation; 194 | } else { 195 | throw SIPL::SIPLException("Trying to fetch non existing data from TSFOutput", __LINE__, __FILE__); 196 | } 197 | } 198 | 199 | char * TSFOutput::getCenterlineVoxels() { 200 | if(hostHasCenterlineVoxels) { 201 | return centerlineVoxels; 202 | } else if(deviceHasCenterlineVoxels) { 203 | // Transfer data from device to host 204 | cl::size_t<3> origin; 205 | origin[0] = 0; 206 | origin[1] = 0; 207 | origin[2] = 0; 208 | cl::size_t<3> region; 209 | region[0] = size->x; 210 | region[1] = size->y; 211 | region[2] = size->z; 212 | centerlineVoxels = new char[size->x*size->y*size->z]; 213 | ocl->queue.enqueueReadImage(*oclCenterlineVoxels,CL_TRUE, origin, region, 0, 0, centerlineVoxels); 214 | hostHasCenterlineVoxels = true; 215 | return centerlineVoxels; 216 | } else { 217 | throw SIPL::SIPLException("Trying to fetch non existing data from TSFOutput", __LINE__, __FILE__); 218 | } 219 | } 220 | 221 | SIPL::int3 * TSFOutput::getSize() { 222 | return size; 223 | } 224 | 225 | SIPL::int3 TSFOutput::getShiftVector() const { 226 | return shiftVector; 227 | } 228 | 229 | void TSFOutput::setShiftVector(SIPL::int3 shiftVector) { 230 | this->shiftVector = shiftVector; 231 | } 232 | 233 | SIPL::float3 TSFOutput::getSpacing() const { 234 | return spacing; 235 | } 236 | 237 | void TSFOutput::setSpacing(SIPL::float3 spacing) { 238 | this->spacing = spacing; 239 | } 240 | -------------------------------------------------------------------------------- /inputOutput.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_OUTPUT_H 2 | #define INPUT_OUTPUT_H 3 | 4 | #include "SIPL/Types.hpp" 5 | #include 6 | #include "parameters.hpp" 7 | #include "commons.hpp" 8 | using namespace SIPL; 9 | 10 | class TSFOutput { 11 | public: 12 | TSFOutput(oul::DeviceCriteria criteria, SIPL::int3 * size, bool TDFis16bit = false); 13 | bool hasSegmentation() { return deviceHasSegmentation || hostHasSegmentation; }; 14 | bool hasCenterlineVoxels() { return deviceHasCenterlineVoxels || hostHasCenterlineVoxels; }; 15 | bool hasTDF() { return deviceHasTDF || hostHasTDF; }; 16 | void setTDF(cl::Image3D *); 17 | void setSegmentation(cl::Image3D *); 18 | void setCenterlineVoxels(cl::Image3D *); 19 | void setTDF(float *); 20 | void setSegmentation(char *); 21 | void setCenterlineVoxels(char *); 22 | void setSize(SIPL::int3 *); 23 | char * getSegmentation(); 24 | char * getCenterlineVoxels(); 25 | float * getTDF(); 26 | SIPL::int3 * getSize(); 27 | ~TSFOutput(); 28 | SIPL::int3 getShiftVector() const; 29 | void setShiftVector(SIPL::int3 shiftVector); 30 | SIPL::float3 getSpacing() const; 31 | void setSpacing(SIPL::float3 spacing); 32 | oul::Context *getContext(); 33 | private: 34 | oul::Context *context; 35 | cl::Image3D* oclCenterlineVoxels; 36 | cl::Image3D* oclSegmentation; 37 | cl::Image3D* oclTDF; 38 | SIPL::int3* size; 39 | SIPL::float3 spacing; 40 | SIPL::int3 shiftVector; 41 | bool TDFis16bit; 42 | bool hostHasSegmentation; 43 | bool hostHasCenterlineVoxels; 44 | bool hostHasTDF; 45 | bool deviceHasTDF; 46 | bool deviceHasCenterlineVoxels; 47 | bool deviceHasSegmentation; 48 | char* segmentation; 49 | char* centerlineVoxels; 50 | float* TDF; 51 | OpenCL* ocl; 52 | }; 53 | 54 | void writeToVtkFile(paramList ¶meters, std::vector vertices, std::vector edges); 55 | 56 | void writeDataToDisk(TSFOutput * output, std::string storageDirectory, std::string name); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "tube-segmentation.hpp" 2 | #include "SIPL/Core.hpp" 3 | #include "tsf-config.h" 4 | 5 | 6 | int main(int argc, char ** argv) { 7 | if(argc == 1 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { 8 | // Print help message 9 | std::cout << std::endl; 10 | std::cout << "Tube Segmentation Framework" << std::endl; 11 | std::cout << "===========================" << std::endl; 12 | std::cout << "Copyright Erik Smistad 2013 - See file LICENSE for license information." << std::endl; 13 | std::cout << std::endl; 14 | std::cout << "Usage: " << argv[0] << " inputFilename.mhd " << std::endl; 15 | std::cout << std::endl; 16 | std::cout << "Example: " << argv[0] << " tests/data/synthetic/dataset_1/noisy.mhd --parameters Synthetic-Vascusynth --display" << std::endl; 17 | std::cout << std::endl; 18 | std::cout << "Available parameter presets: " << std::endl; 19 | std::cout << "* Lung-Airways-CT" << std::endl; 20 | std::cout << "* Neuro-Vessels-USA" << std::endl; 21 | std::cout << "* Neuro-Vessels-MRA" << std::endl; 22 | std::cout << "* AAA-Vessels-CT" << std::endl; 23 | std::cout << "* Liver-Vessels-CT" << std::endl; 24 | std::cout << "* Synthetic-Vascusynth" << std::endl; 25 | std::cout << std::endl; 26 | std::cout << "The parameter preset is set with the program argument \"--parameters \"." << std::endl; 27 | std::cout << std::endl; 28 | std::cout << "Available parameters: " << std::endl; 29 | printAllParameters(); 30 | exit(-1); 31 | } 32 | 33 | // Load default parameters and parse parameters from program arguments 34 | paramList parameters = getParameters(argc, argv); 35 | std::string filename = argv[1]; 36 | 37 | 38 | TSFOutput * output; 39 | try { 40 | output = run(filename, parameters, std::string(KERNELS_DIR)); 41 | } catch(SIPL::SIPLException &e) { 42 | std::cout << e.what() << std::endl; 43 | 44 | return -1; 45 | } 46 | 47 | if(getParamBool(parameters, "display")) { 48 | // Visualize result 49 | SIPL::int3 * size = output->getSize(); 50 | SIPL::Volume * result = new SIPL::Volume(size->x, size->y, size->z); 51 | float * TDF; 52 | char * centerline; 53 | char * segmentation; 54 | if(output->hasTDF()) 55 | TDF = output->getTDF(); 56 | if(output->hasCenterlineVoxels()) 57 | centerline = output->getCenterlineVoxels(); 58 | if(output->hasSegmentation()) 59 | segmentation = output->getSegmentation(); 60 | for(int i = 0; i < result->getTotalSize(); i++) { 61 | SIPL::float3 v; 62 | if(output->hasTDF()) 63 | v.x = TDF[i]; 64 | if(output->hasCenterlineVoxels()) 65 | v.y = centerline[i] ? 1.0:0.0; 66 | if(output->hasSegmentation()) 67 | v.z = segmentation[i] ? 1.0:0.0; 68 | result->set(i,v); 69 | } 70 | result->showMIP(SIPL::Y); 71 | } 72 | 73 | // free data 74 | output->~TSFOutput(); 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /parallelCenterlineExtraction.cpp: -------------------------------------------------------------------------------- 1 | #include "parallelCenterlineExtraction.hpp" 2 | #include "tube-segmentation.hpp" 3 | #include 4 | #include 5 | #include "inputOutput.hpp" 6 | #include "OpenCLUtilityLibrary/HistogramPyramids.hpp" 7 | #include "eigenanalysisOfHessian.hpp" 8 | #ifdef CPP11 9 | #include 10 | using std::unordered_set; 11 | #else 12 | #include 13 | using boost::unordered_set; 14 | #endif 15 | using namespace cl; 16 | using namespace SIPL; 17 | #define MAX(a,b) a > b ? a : b 18 | 19 | #define LPOS(a,b,c) (a)+(b)*(size.x)+(c)*(size.x*size.y) 20 | #define POS(pos) pos.x+pos.y*size.x+pos.z*size.x*size.y 21 | #define M(a,b,c) 1-sqrt(pow(T.Fx[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fy[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fz[a+b*size.x+c*size.x*size.y],2.0f)) 22 | #define SQR_MAG(pos) sqrt(pow(T.Fx[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fy[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fz[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 23 | #define SQR_MAG_SMALL(pos) sqrt(pow(T.FxSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FySmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FzSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 24 | 25 | 26 | class Edge; 27 | class Node { 28 | public: 29 | int3 pos; 30 | std::vector edges; 31 | 32 | }; 33 | 34 | class Edge { 35 | public: 36 | Node * source; 37 | Node * target; 38 | std::vector removedNodes; 39 | float distance; 40 | }; 41 | 42 | class EdgeComparator { 43 | public: 44 | bool operator()(Edge * a, Edge *b) const { 45 | return a->distance > b->distance; 46 | } 47 | }; 48 | 49 | 50 | void removeEdge(Node * n, Node * remove) { 51 | std::vector edges; 52 | for(int i = 0; i < n->edges.size(); i++) { 53 | Edge * e = n->edges[i]; 54 | if(e->target != remove) { 55 | edges.push_back(e); 56 | } 57 | } 58 | n->edges = edges; 59 | } 60 | 61 | void restoreNodes(Edge * e, std::vector &finalGraph) { 62 | if(e->removedNodes.size() == 0) 63 | return; 64 | 65 | // Remove e from e->source 66 | removeEdge(e->source, e->target); 67 | Node * previous = e->source; 68 | for(int k = 0; k < e->removedNodes.size(); k++) { 69 | Edge * newEdge = new Edge; 70 | // Create edge from source to k or k-1 to k 71 | newEdge->source = previous; 72 | newEdge->target = e->removedNodes[k]; 73 | previous->edges.push_back(newEdge); 74 | previous = e->removedNodes[k]; 75 | finalGraph.push_back(e->removedNodes[k]); 76 | 77 | } 78 | // Create edge from last k to target 79 | Edge * newEdge = new Edge; 80 | newEdge->source = previous; 81 | newEdge->target = e->target; 82 | previous->edges.push_back(newEdge); 83 | } 84 | 85 | std::vector minimumSpanningTreePCE( 86 | int root, 87 | std::vector &graph, 88 | int3 &size, 89 | unordered_set &visited 90 | ) { 91 | std::vector result; 92 | std::priority_queue, EdgeComparator> queue; 93 | 94 | // Start with graph[0] 95 | result.push_back(graph[root]); 96 | visited.insert(POS(graph[root]->pos)); 97 | 98 | // Add edges to priority queue 99 | for(int i = 0; i < graph[root]->edges.size(); i++) { 100 | Edge * en = graph[root]->edges[i]; 101 | queue.push(en); 102 | } 103 | graph[root]->edges.clear(); 104 | 105 | while(!queue.empty()) { 106 | Edge * e = queue.top(); 107 | queue.pop(); 108 | 109 | if(visited.find(POS(e->target->pos)) != visited.end()) 110 | continue; // already visited 111 | 112 | // Add all edges of e->target to queue if targets have not been added 113 | for(int i = 0; i < e->target->edges.size(); i++) { 114 | Edge * en = e->target->edges[i]; 115 | if(visited.find(POS(en->target->pos)) == visited.end()) { 116 | queue.push(en); 117 | } 118 | } 119 | 120 | e->target->edges.clear(); // Remove all edges first 121 | e->source->edges.push_back(e); // Add edge to source 122 | result.push_back(e->target); // Add target to result 123 | visited.insert(POS(e->target->pos)); 124 | } 125 | 126 | return result; 127 | } 128 | 129 | void removeLoops( 130 | std::vector &vertices, 131 | std::vector &edges, 132 | int3 &size 133 | ) { 134 | 135 | std::vector nodes; 136 | for(int i = 0; i < vertices.size(); i++) { 137 | Node * n = new Node; 138 | n->pos = vertices[i]; 139 | nodes.push_back(n); 140 | } 141 | for(int i = 0; i < edges.size(); i++) { 142 | Node * a = nodes[edges[i].x]; 143 | Node * b = nodes[edges[i].y]; 144 | Edge * e = new Edge; 145 | e->distance = a->pos.distance(b->pos); 146 | e->source = a; 147 | e->target = b; 148 | a->edges.push_back(e); 149 | 150 | Edge * e2 = new Edge; 151 | e2->distance = a->pos.distance(b->pos); 152 | e2->source = b; 153 | e2->target = a; 154 | b->edges.push_back(e2); 155 | } 156 | 157 | // Create graph 158 | std::vector graph; 159 | 160 | // Remove all nodes with degree 2 and add them to edge 161 | for(int i = 0; i < nodes.size(); i++) { 162 | Node * n = nodes[i]; 163 | if(n->edges.size() == 2) { 164 | // calculate distance 165 | float distance = n->edges[0]->distance+n->edges[1]->distance; 166 | Node * a = n->edges[0]->target; 167 | Node * b = n->edges[1]->target; 168 | // Fuse the two nodes together 169 | Edge * e = new Edge; 170 | e->source = a; 171 | e->target = b; 172 | e->distance = distance; 173 | // Add removed nodes from previous edges 174 | for(int j = n->edges[0]->removedNodes.size()-1; j >= 0; j--) { 175 | e->removedNodes.push_back(n->edges[0]->removedNodes[j]); 176 | } 177 | e->removedNodes.push_back(n); 178 | for(int j = 0; j < n->edges[1]->removedNodes.size(); j++) { 179 | e->removedNodes.push_back(n->edges[1]->removedNodes[j]); 180 | } 181 | 182 | Edge * e2 = new Edge; 183 | e2->source = b; 184 | e2->target = a; 185 | e2->distance = distance; 186 | // Add removed nodes from previous edges 187 | for(int j = n->edges[1]->removedNodes.size()-1; j >= 0; j--) { 188 | e2->removedNodes.push_back(n->edges[1]->removedNodes[j]); 189 | } 190 | e2->removedNodes.push_back(n); 191 | for(int j = 0; j < n->edges[0]->removedNodes.size(); j++) { 192 | e2->removedNodes.push_back(n->edges[0]->removedNodes[j]); 193 | } 194 | 195 | removeEdge(a,n); 196 | removeEdge(b,n); 197 | n->edges.clear(); 198 | a->edges.push_back(e); 199 | b->edges.push_back(e2); 200 | } else { 201 | // add node to graph 202 | graph.push_back(n); 203 | } 204 | } 205 | 206 | // Do MST with edge distance as cost 207 | int sizeBeforeMST = graph.size(); 208 | if(sizeBeforeMST == 0) { 209 | throw SIPL::SIPLException("Centerline graph size is 0! Can't continue. Maybe lower min-tree-length?", __LINE__,__FILE__); 210 | } 211 | unordered_set visited; 212 | std::vector newGraph = minimumSpanningTreePCE(0, graph, size, visited); 213 | int sizeAfterMST = newGraph.size(); 214 | 215 | while(newGraph.size() < graph.size()) { 216 | // Some nodes were not visited: find new root 217 | int root; 218 | for(int i = 0; i < graph.size(); i++) { 219 | if(visited.find(POS(graph[i]->pos)) == visited.end()) { 220 | // i has not been used 221 | root = i; 222 | } 223 | } 224 | 225 | std::vector newGraph2 = minimumSpanningTreePCE(root, graph, size, visited); 226 | for(int i = 0; i < newGraph2.size(); i++) { 227 | newGraph.push_back(newGraph2[i]); 228 | } 229 | int sizeAfterMST = newGraph.size(); 230 | } 231 | 232 | // Restore graph 233 | // For all edges that are in the MST graph: get nodes that was on these edges 234 | std::vector finalGraph; 235 | for(int i = 0; i < newGraph.size(); i++) { 236 | Node * n = newGraph[i]; 237 | finalGraph.push_back(n); 238 | std::vector nEdges = n->edges; 239 | for(int j = 0; j < nEdges.size(); j++) { 240 | Edge * e = nEdges[j]; 241 | restoreNodes(e, finalGraph); 242 | } 243 | } 244 | 245 | 246 | std::vector newVertices; 247 | std::vector newEdges; 248 | unordered_map added; 249 | int counter = 0; 250 | for(int i = 0; i < finalGraph.size(); i++) { 251 | Node * n = finalGraph[i]; 252 | int nIndex; 253 | if(added.find(POS(n->pos)) != added.end()) { 254 | // already has been added 255 | // fetch index 256 | nIndex = added[POS(n->pos)]; 257 | } else { 258 | newVertices.push_back(n->pos); 259 | added[POS(n->pos)] = counter; 260 | nIndex = counter; 261 | counter++; 262 | } 263 | for(int j = 0; j < n->edges.size(); j++) { 264 | Edge * e = n->edges[j]; 265 | // add edge from n to n_j 266 | 267 | int tIndex; 268 | if(added.find(POS(e->target->pos)) != added.end()) { 269 | // already has been added 270 | // fetch index 271 | tIndex = added[POS(e->target->pos)]; 272 | } else { 273 | newVertices.push_back(e->target->pos); 274 | added[POS(e->target->pos)] = counter; 275 | tIndex = counter; 276 | counter++; 277 | } 278 | newEdges.push_back(SIPL::int2(nIndex, tIndex)); 279 | } 280 | } 281 | 282 | // Cleanup 283 | vertices = newVertices; 284 | edges = newEdges; 285 | } 286 | 287 | char * createCenterlineVoxels( 288 | std::vector &vertices, 289 | std::vector &edges, 290 | float * radius, 291 | int3 &size 292 | ) { 293 | const int totalSize = size.x*size.y*size.z; 294 | char * centerlines = new char[totalSize](); 295 | 296 | for(int i = 0; i < edges.size(); i++) { 297 | int3 a = vertices[edges[i].x]; 298 | int3 b = vertices[edges[i].y]; 299 | float distance = a.distance(b); 300 | float3 direction(b.x-a.x,b.y-a.y,b.z-a.z); 301 | int n = ceil(distance); 302 | float avgRadius = 0.0f; 303 | for(int j = 0; j < n; j++) { 304 | float ratio = (float)j/n; 305 | float3 pos = a+direction*ratio; 306 | int3 intPos(round(pos.x), round(pos.y), round(pos.z)); 307 | centerlines[POS(intPos)] = 1; 308 | avgRadius += radius[POS(intPos)]; 309 | } 310 | avgRadius /= n; 311 | 312 | for(int j = 0; j < n; j++) { 313 | float ratio = (float)j/n; 314 | float3 pos = a+direction*ratio; 315 | int3 intPos(round(pos.x), round(pos.y), round(pos.z)); 316 | radius[POS(intPos)] = avgRadius; 317 | } 318 | 319 | } 320 | 321 | return centerlines; 322 | } 323 | Image3D runNewCenterlineAlgWithoutOpenCL(OpenCL &ocl, SIPL::int3 size, paramList ¶meters, Image3D &vectorField, Image3D &TDF, Image3D &radius) { 324 | const int totalSize = size.x*size.y*size.z; 325 | const bool no3Dwrite = !getParamBool(parameters, "3d_write"); 326 | const int cubeSize = getParam(parameters, "cube-size"); 327 | const int minTreeLength = getParam(parameters, "min-tree-length"); 328 | const float Thigh = getParam(parameters, "tdf-high"); 329 | const float minAvgTDF = getParam(parameters, "min-mean-tdf"); 330 | const float maxDistance = getParam(parameters, "max-distance"); 331 | 332 | cl::size_t<3> offset; 333 | offset[0] = 0; 334 | offset[1] = 0; 335 | offset[2] = 0; 336 | cl::size_t<3> region; 337 | region[0] = size.x; 338 | region[1] = size.y; 339 | region[2] = size.z; 340 | 341 | // Transfer TDF, vectorField and radius to host 342 | TubeSegmentation T; 343 | T.Fx = new float[totalSize]; 344 | T.Fy = new float[totalSize]; 345 | T.Fz = new float[totalSize]; 346 | T.TDF = new float[totalSize]; 347 | 348 | if(!getParamBool(parameters, "16bit-vectors")) { 349 | // 32 bit vector fields 350 | float * Fs = new float[totalSize*4]; 351 | ocl.queue.enqueueReadImage(vectorField, CL_TRUE, offset, region, 0, 0, Fs); 352 | #pragma omp parallel for 353 | for(int i = 0; i < totalSize; i++) { 354 | T.Fx[i] = Fs[i*4]; 355 | T.Fy[i] = Fs[i*4+1]; 356 | T.Fz[i] = Fs[i*4+2]; 357 | } 358 | delete[] Fs; 359 | ocl.queue.enqueueReadImage(TDF, CL_TRUE, offset, region, 0, 0, T.TDF); 360 | } else { 361 | // 16 bit vector fields 362 | short * Fs = new short[totalSize*4]; 363 | ocl.queue.enqueueReadImage(vectorField, CL_TRUE, offset, region, 0, 0, Fs); 364 | #pragma omp parallel for 365 | for(int i = 0; i < totalSize; i++) { 366 | T.Fx[i] = MAX(-1.0f, Fs[i*4] / 32767.0f); 367 | T.Fy[i] = MAX(-1.0f, Fs[i*4+1] / 32767.0f);; 368 | T.Fz[i] = MAX(-1.0f, Fs[i*4+2] / 32767.0f); 369 | } 370 | delete[] Fs; 371 | 372 | // Convert 16 bit TDF to 32 bit 373 | unsigned short * tempTDF = new unsigned short[totalSize]; 374 | ocl.queue.enqueueReadImage(TDF, CL_TRUE, offset, region, 0, 0, tempTDF); 375 | #pragma omp parallel for 376 | for(int i = 0; i < totalSize; i++) { 377 | T.TDF[i] = (float)tempTDF[i] / 65535.0f; 378 | } 379 | delete[] tempTDF; 380 | } 381 | T.radius = new float[totalSize]; 382 | ocl.queue.enqueueReadImage(radius, CL_TRUE, offset, region, 0, 0, T.radius); 383 | 384 | // Get candidate points 385 | std::vector candidatePoints; 386 | #pragma omp parallel for 387 | for(int z = 1; z < size.z-1; z++) { 388 | for(int y = 1; y < size.y-1; y++) { 389 | for(int x = 1; x < size.x-1; x++) { 390 | int3 pos(x,y,z); 391 | if(T.TDF[POS(pos)] >= Thigh) { 392 | #pragma omp critical 393 | candidatePoints.push_back(pos); 394 | } 395 | }}} 396 | std::cout << "candidate points: " << candidatePoints.size() << std::endl; 397 | 398 | unordered_set filteredPoints; 399 | #pragma omp parallel for 400 | for(int i = 0; i < candidatePoints.size(); i++) { 401 | int3 pos = candidatePoints[i]; 402 | // Filter candidate points 403 | const float thetaLimit = 0.5f; 404 | const float radii = T.radius[POS(pos)]; 405 | const int maxD = std::max(std::min((float)round(radii), 5.0f), 1.0f); 406 | bool invalid = false; 407 | 408 | float3 e1 = getTubeDirection(T, pos, size); 409 | 410 | for(int a = -maxD; a <= maxD; a++) { 411 | for(int b = -maxD; b <= maxD; b++) { 412 | for(int c = -maxD; c <= maxD; c++) { 413 | if(a == 0 && b == 0 && c == 0) 414 | continue; 415 | float3 r(a,b,c); 416 | float length = r.length(); 417 | int3 n(pos.x + r.x,pos.y+r.y,pos.z+r.z); 418 | if(!inBounds(n,size)) 419 | continue; 420 | float dp = e1.dot(r); 421 | float3 r_projected(r.x-e1.x*dp,r.y-e1.y*dp,r.z-e1.z*dp); 422 | float3 rn = r.normalize(); 423 | float3 r_projected_n = r_projected.normalize(); 424 | float theta = acos(rn.dot(r_projected_n)); 425 | if((theta < thetaLimit && length < maxD)) { 426 | if(SQR_MAG(n) < SQR_MAG(pos)) { 427 | invalid = true; 428 | break; 429 | } 430 | 431 | } 432 | }}} 433 | if(!invalid) { 434 | #pragma omp critical 435 | filteredPoints.insert(POS(pos)); 436 | } 437 | } 438 | candidatePoints.clear(); 439 | std::cout << "filtered points: " << filteredPoints.size() << std::endl; 440 | 441 | std::vector centerpoints; 442 | for(int z = 0; z < size.z/cubeSize; z++) { 443 | for(int y = 0; y < size.y/cubeSize; y++) { 444 | for(int x = 0; x < size.x/cubeSize; x++) { 445 | int3 bestPos; 446 | float bestTDF = 0.0f; 447 | int3 readPos( 448 | x*cubeSize, 449 | y*cubeSize, 450 | z*cubeSize 451 | ); 452 | bool found = false; 453 | for(int a = 0; a < cubeSize; a++) { 454 | for(int b = 0; b < cubeSize; b++) { 455 | for(int c = 0; c < cubeSize; c++) { 456 | int3 pos = readPos + int3(a,b,c); 457 | if(filteredPoints.find(POS(pos)) != filteredPoints.end()) { 458 | float tdf = T.TDF[POS(pos)]; 459 | if(tdf > bestTDF) { 460 | found = true; 461 | bestTDF = tdf; 462 | bestPos = pos; 463 | } 464 | } 465 | }}} 466 | if(found) { 467 | #pragma omp critical 468 | centerpoints.push_back(bestPos); 469 | } 470 | }}} 471 | 472 | int nofPoints = centerpoints.size(); 473 | std::cout << "filtered points: " < edges; 475 | 476 | // Do linking 477 | for(int i = 0; i < nofPoints;i++) { 478 | int3 xa = centerpoints[i]; 479 | SIPL::int2 bestPair; 480 | float shortestDistance = maxDistance*2; 481 | bool validPairFound = false; 482 | 483 | for(int j = 0; j < nofPoints;j++) { 484 | if(i == j) 485 | continue; 486 | int3 xb = centerpoints[j]; 487 | 488 | int db = round(xa.distance(xb)); 489 | if(db >= shortestDistance) 490 | continue; 491 | for(int k = 0; k < j;k++) { 492 | if(k == i) 493 | continue; 494 | int3 xc = centerpoints[k]; 495 | 496 | int dc = round(xa.distance(xc)); 497 | 498 | if(db+dc < shortestDistance) { 499 | // Check angle 500 | int3 ab = (xb-xa); 501 | int3 ac = (xc-xa); 502 | float angle = acos(ab.normalize().dot(ac.normalize())); 503 | //printf("angle: %f\n", angle); 504 | if(angle < 2.0f) // 120 degrees 505 | //if(angle < 1.57f) // 90 degrees 506 | continue; 507 | 508 | // Check avg TDF for a-b 509 | float avgTDF = 0.0f; 510 | for(int l = 0; l <= db; l++) { 511 | float alpha = (float)l/db; 512 | int3 p((int)round(xa.x+ab.x*alpha),(int)round(xa.y+ab.y*alpha),(int)round(xa.z+ab.z*alpha)); 513 | float t = T.TDF[POS(p)]; 514 | avgTDF += t; 515 | } 516 | avgTDF /= db+1; 517 | if(avgTDF < minAvgTDF) 518 | continue; 519 | 520 | avgTDF = 0.0f; 521 | 522 | // Check avg TDF for a-c 523 | for(int l = 0; l <= dc; l++) { 524 | float alpha = (float)l/dc; 525 | int3 p((int)round(xa.x+ac.x*alpha),(int)round(xa.y+ac.y*alpha),(int)round(xa.z+ac.z*alpha)); 526 | float t = T.TDF[POS(p)]; 527 | avgTDF += t; 528 | } 529 | avgTDF /= dc+1; 530 | 531 | if(avgTDF < minAvgTDF) 532 | continue; 533 | 534 | validPairFound = true; 535 | bestPair.x = j; 536 | bestPair.y = k; 537 | shortestDistance = db+dc; 538 | } 539 | } // k 540 | }// j 541 | 542 | if(validPairFound) { 543 | // Store edges 544 | SIPL::int2 edge(i, bestPair.x); 545 | SIPL::int2 edge2(i, bestPair.y); 546 | edges.push_back(edge); 547 | edges.push_back(edge2); 548 | } 549 | } // i 550 | std::cout << "nr of edges: " << edges.size() << std::endl; 551 | 552 | // Do graph component labeling 553 | // Create initial labels 554 | int * labels = new int[nofPoints]; 555 | for(int i = 0; i < nofPoints; i++) { 556 | labels[i] = i; 557 | } 558 | 559 | // Do iteratively using edges until no more changes 560 | bool changeDetected = true; 561 | while(changeDetected) { 562 | changeDetected = false; 563 | for(int i = 0; i < edges.size(); i++) { 564 | SIPL::int2 edge = edges[i]; 565 | if(labels[edge.x] != labels[edge.y]) { 566 | changeDetected = true; 567 | if(labels[edge.x] < labels[edge.y]) { 568 | labels[edge.x] = labels[edge.y]; 569 | } else { 570 | labels[edge.y] = labels[edge.x]; 571 | } 572 | } 573 | } 574 | } 575 | 576 | 577 | // Calculate length of each label 578 | int * lengths = new int[nofPoints](); 579 | for(int i = 0; i < nofPoints; i++) { 580 | lengths[labels[i]]++; 581 | } 582 | std::vector vertices = centerpoints; 583 | 584 | // Select wanted parts of centerline 585 | 586 | std::vector edges2; 587 | int counter = nofPoints; 588 | int maxEdgeDistance = getParam(parameters, "max-edge-distance"); 589 | for(int i = 0; i < edges.size(); i++) { 590 | if(lengths[labels[edges[i].x]] >= minTreeLength && lengths[labels[edges[i].y]] >= minTreeLength ) { 591 | // Check length of edge 592 | int3 A = vertices[edges[i].x]; 593 | int3 B = vertices[edges[i].y]; 594 | float distance = A.distance(B); 595 | if(getParamStr(parameters, "centerline-vtk-file") != "off" && 596 | distance > maxEdgeDistance) { 597 | float3 direction(B.x-A.x,B.y-A.y,B.z-A.z); 598 | float3 Af(A.x,A.y,A.z); 599 | int previous = edges[i].x; 600 | for(int j = maxEdgeDistance; j < distance; j += maxEdgeDistance) { 601 | float3 newPos = Af + ((float)j/distance)*direction; 602 | int3 newVertex(round(newPos.x), round(newPos.y), round(newPos.z)); 603 | // Create new vertex 604 | vertices.push_back(newVertex); 605 | // Add new edge 606 | SIPL::int2 edge(previous, counter); 607 | edges2.push_back(edge); 608 | previous = counter; 609 | counter++; 610 | } 611 | // Connect previous vertex to B 612 | SIPL::int2 edge(previous, edges[i].y); 613 | edges2.push_back(edge); 614 | } else { 615 | edges2.push_back(edges[i]); 616 | } 617 | } 618 | } 619 | edges = edges2; 620 | 621 | // Remove loops from graph 622 | if(getParamBool(parameters, "loop-removal")) 623 | removeLoops(vertices, edges, size); 624 | 625 | ocl.queue.finish(); 626 | char * centerlinesData = createCenterlineVoxels(vertices, edges, T.radius, size); 627 | Image3D centerlines= Image3D( 628 | ocl.context, 629 | CL_MEM_READ_WRITE, 630 | ImageFormat(CL_R, CL_SIGNED_INT8), 631 | size.x, size.y, size.z 632 | ); 633 | 634 | ocl.queue.enqueueWriteImage( 635 | centerlines, 636 | CL_FALSE, 637 | offset, 638 | region, 639 | 0, 0, 640 | centerlinesData 641 | ); 642 | ocl.queue.enqueueWriteImage( 643 | radius, 644 | CL_FALSE, 645 | offset, 646 | region, 647 | 0, 0, 648 | T.radius 649 | ); 650 | 651 | if(getParamStr(parameters, "centerline-vtk-file") != "off") { 652 | writeToVtkFile(parameters, vertices, edges); 653 | } 654 | 655 | ocl.queue.finish(); 656 | 657 | delete[] T.TDF; 658 | delete[] T.Fx; 659 | delete[] T.Fy; 660 | delete[] T.Fz; 661 | delete[] T.radius; 662 | delete[] centerlinesData; 663 | 664 | return centerlines; 665 | } 666 | 667 | Image3D runNewCenterlineAlg(OpenCL &ocl, SIPL::int3 size, paramList ¶meters, Image3D &vectorField, Image3D &TDF, Image3D &radius) { 668 | if(ocl.platform.getInfo().substr(0,5) == "Apple") { 669 | std::cout << "Apple platform detected. Running centerline extraction without OpenCL." << std::endl; 670 | return runNewCenterlineAlgWithoutOpenCL(ocl,size,parameters,vectorField,TDF,radius); 671 | } 672 | const int totalSize = size.x*size.y*size.z; 673 | const bool no3Dwrite = !getParamBool(parameters, "3d_write"); 674 | const int cubeSize = getParam(parameters, "cube-size"); 675 | const int minTreeLength = getParam(parameters, "min-tree-length"); 676 | const float Thigh = getParam(parameters, "tdf-high"); 677 | const float Tmean = getParam(parameters, "min-mean-tdf"); 678 | const float maxDistance = getParam(parameters, "max-distance"); 679 | 680 | cl::size_t<3> offset; 681 | offset[0] = 0; 682 | offset[1] = 0; 683 | offset[2] = 0; 684 | cl::size_t<3> region; 685 | region[0] = size.x; 686 | region[1] = size.y; 687 | region[2] = size.z; 688 | 689 | Kernel candidatesKernel(ocl.program, "findCandidateCenterpoints"); 690 | Kernel candidates2Kernel(ocl.program, "findCandidateCenterpoints2"); 691 | Kernel ddKernel(ocl.program, "dd"); 692 | Kernel initCharBuffer(ocl.program, "initCharBuffer"); 693 | 694 | cl::Event startEvent, endEvent; 695 | cl_ulong start, end; 696 | if(getParamBool(parameters, "timing")) { 697 | ocl.queue.enqueueMarker(&startEvent); 698 | } 699 | Image3D * centerpointsImage2 = new Image3D( 700 | ocl.context, 701 | CL_MEM_READ_WRITE, 702 | ImageFormat(CL_R, CL_SIGNED_INT8), 703 | size.x, size.y, size.z 704 | ); 705 | ocl.GC->addMemoryObject(centerpointsImage2); 706 | Buffer vertices; 707 | int sum = 0; 708 | 709 | if(no3Dwrite) { 710 | Buffer * centerpoints = new Buffer( 711 | ocl.context, 712 | CL_MEM_READ_WRITE, 713 | sizeof(char)*totalSize 714 | ); 715 | ocl.GC->addMemoryObject(centerpoints); 716 | 717 | candidatesKernel.setArg(0, TDF); 718 | candidatesKernel.setArg(1, *centerpoints); 719 | candidatesKernel.setArg(2, Thigh); 720 | ocl.queue.enqueueNDRangeKernel( 721 | candidatesKernel, 722 | NullRange, 723 | NDRange(size.x,size.y,size.z), 724 | NullRange 725 | ); 726 | 727 | oul::HistogramPyramid3DBuffer hp3(ocl); 728 | hp3.create(*centerpoints, size.x, size.y, size.z); 729 | 730 | candidates2Kernel.setArg(0, TDF); 731 | candidates2Kernel.setArg(1, radius); 732 | candidates2Kernel.setArg(2, vectorField); 733 | Buffer * centerpoints2 = new Buffer( 734 | ocl.context, 735 | CL_MEM_READ_WRITE, 736 | sizeof(char)*totalSize 737 | ); 738 | ocl.GC->addMemoryObject(centerpoints2); 739 | initCharBuffer.setArg(0, *centerpoints2); 740 | ocl.queue.enqueueNDRangeKernel( 741 | initCharBuffer, 742 | NullRange, 743 | NDRange(totalSize), 744 | NullRange 745 | ); 746 | 747 | candidates2Kernel.setArg(3, *centerpoints2); 748 | std::cout << "candidates: " << hp3.getSum() << std::endl; 749 | if(hp3.getSum() <= 0 || hp3.getSum() > 0.5*totalSize) { 750 | throw SIPL::SIPLException("The number of candidate voxels is too low or too high. Something went wrong... Wrong parameters? Out of memory?", __LINE__, __FILE__); 751 | } 752 | hp3.traverse(candidates2Kernel, 4); 753 | ocl.queue.finish(); 754 | hp3.deleteHPlevels(); 755 | ocl.GC->deleteMemoryObject(centerpoints); 756 | ocl.queue.enqueueCopyBufferToImage( 757 | *centerpoints2, 758 | *centerpointsImage2, 759 | 0, 760 | offset, 761 | region 762 | ); 763 | ocl.queue.finish(); 764 | ocl.GC->deleteMemoryObject(centerpoints2); 765 | 766 | if(getParamBool(parameters, "centerpoints-only")) { 767 | return *centerpointsImage2; 768 | } 769 | ddKernel.setArg(0, TDF); 770 | ddKernel.setArg(1, *centerpointsImage2); 771 | ddKernel.setArg(3, cubeSize); 772 | Buffer * centerpoints3 = new Buffer( 773 | ocl.context, 774 | CL_MEM_READ_WRITE, 775 | sizeof(char)*totalSize 776 | ); 777 | ocl.GC->addMemoryObject(centerpoints3); 778 | initCharBuffer.setArg(0, *centerpoints3); 779 | ocl.queue.enqueueNDRangeKernel( 780 | initCharBuffer, 781 | NullRange, 782 | NDRange(totalSize), 783 | NullRange 784 | ); 785 | ddKernel.setArg(2, *centerpoints3); 786 | ocl.queue.enqueueNDRangeKernel( 787 | ddKernel, 788 | NullRange, 789 | NDRange(ceil((float)size.x/cubeSize),ceil((float)size.y/cubeSize),ceil((float)size.z/cubeSize)), 790 | NullRange 791 | ); 792 | ocl.queue.finish(); 793 | ocl.GC->deleteMemoryObject(centerpointsImage2); 794 | 795 | // Construct HP of centerpointsImage 796 | oul::HistogramPyramid3DBuffer hp(ocl); 797 | hp.create(*centerpoints3, size.x, size.y, size.z); 798 | sum = hp.getSum(); 799 | std::cout << "number of vertices detected " << sum << std::endl; 800 | 801 | // Run createPositions kernel 802 | vertices = hp.createPositionBuffer(); 803 | ocl.queue.finish(); 804 | hp.deleteHPlevels(); 805 | ocl.GC->deleteMemoryObject(centerpoints3); 806 | } else { 807 | Kernel init3DImage(ocl.program, "init3DImage"); 808 | init3DImage.setArg(0, *centerpointsImage2); 809 | ocl.queue.enqueueNDRangeKernel( 810 | init3DImage, 811 | NullRange, 812 | NDRange(size.x,size.y,size.z), 813 | NullRange 814 | ); 815 | 816 | Image3D * centerpointsImage = new Image3D( 817 | ocl.context, 818 | CL_MEM_READ_WRITE, 819 | ImageFormat(CL_R, CL_SIGNED_INT8), 820 | size.x, size.y, size.z 821 | ); 822 | ocl.GC->addMemoryObject(centerpointsImage); 823 | 824 | candidatesKernel.setArg(0, TDF); 825 | candidatesKernel.setArg(1, *centerpointsImage); 826 | candidatesKernel.setArg(2, Thigh); 827 | ocl.queue.enqueueNDRangeKernel( 828 | candidatesKernel, 829 | NullRange, 830 | NDRange(size.x,size.y,size.z), 831 | NDRange(4,4,4) 832 | ); 833 | 834 | 835 | candidates2Kernel.setArg(0, TDF); 836 | candidates2Kernel.setArg(1, radius); 837 | candidates2Kernel.setArg(2, vectorField); 838 | 839 | oul::HistogramPyramid3D hp3(ocl); 840 | hp3.create(*centerpointsImage, size.x, size.y, size.z); 841 | std::cout << "candidates: " << hp3.getSum() << std::endl; 842 | if(hp3.getSum() <= 0 || hp3.getSum() > 0.5*totalSize) { 843 | throw SIPL::SIPLException("The number of candidate voxels is too or too high. Something went wrong... Wrong parameters? Out of memory?", __LINE__, __FILE__); 844 | } 845 | 846 | candidates2Kernel.setArg(3, *centerpointsImage2); 847 | hp3.traverse(candidates2Kernel, 4); 848 | ocl.queue.finish(); 849 | hp3.deleteHPlevels(); 850 | ocl.GC->deleteMemoryObject(centerpointsImage); 851 | 852 | Image3D * centerpointsImage3 = new Image3D( 853 | ocl.context, 854 | CL_MEM_READ_WRITE, 855 | ImageFormat(CL_R, CL_SIGNED_INT8), 856 | size.x, size.y, size.z 857 | ); 858 | ocl.GC->addMemoryObject(centerpointsImage3); 859 | init3DImage.setArg(0, *centerpointsImage3); 860 | ocl.queue.enqueueNDRangeKernel( 861 | init3DImage, 862 | NullRange, 863 | NDRange(size.x,size.y,size.z), 864 | NullRange 865 | ); 866 | 867 | if(getParamBool(parameters, "centerpoints-only")) { 868 | return *centerpointsImage2; 869 | } 870 | ddKernel.setArg(0, TDF); 871 | ddKernel.setArg(1, *centerpointsImage2); 872 | ddKernel.setArg(3, cubeSize); 873 | ddKernel.setArg(2, *centerpointsImage3); 874 | ocl.queue.enqueueNDRangeKernel( 875 | ddKernel, 876 | NullRange, 877 | NDRange(ceil((float)size.x/cubeSize),ceil((float)size.y/cubeSize),ceil((float)size.z/cubeSize)), 878 | NullRange 879 | ); 880 | ocl.queue.finish(); 881 | ocl.GC->deleteMemoryObject(centerpointsImage2); 882 | 883 | // Construct HP of centerpointsImage 884 | oul::HistogramPyramid3D hp(ocl); 885 | hp.create(*centerpointsImage3, size.x, size.y, size.z); 886 | sum = hp.getSum(); 887 | std::cout << "number of vertices detected " << sum << std::endl; 888 | 889 | // Run createPositions kernel 890 | vertices = hp.createPositionBuffer(); 891 | ocl.queue.finish(); 892 | hp.deleteHPlevels(); 893 | ocl.GC->deleteMemoryObject(centerpointsImage3); 894 | } 895 | if(sum < 8) { 896 | throw SIPL::SIPLException("Too few centerpoints detected. Revise parameters.", __LINE__, __FILE__); 897 | } else if(sum >= 16384) { 898 | throw SIPL::SIPLException("Too many centerpoints detected. More cropping of dataset is probably needed.", __LINE__, __FILE__); 899 | } 900 | 901 | if(getParamBool(parameters, "timing")) { 902 | ocl.queue.enqueueMarker(&endEvent); 903 | ocl.queue.finish(); 904 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 905 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 906 | std::cout << "RUNTIME centerpoint extraction: " << (end-start)*1.0e-6 << " ms" << std::endl; 907 | } 908 | 909 | if(getParamBool(parameters, "timing")) { 910 | ocl.queue.enqueueMarker(&startEvent); 911 | } 912 | // Run linking kernel 913 | Image2D edgeTuples = Image2D( 914 | ocl.context, 915 | CL_MEM_READ_WRITE, 916 | ImageFormat(CL_R, CL_UNSIGNED_INT8), 917 | sum, sum 918 | ); 919 | Kernel init2DImage(ocl.program, "init2DImage"); 920 | init2DImage.setArg(0, edgeTuples); 921 | ocl.queue.enqueueNDRangeKernel( 922 | init2DImage, 923 | NullRange, 924 | NDRange(sum, sum), 925 | NullRange 926 | ); 927 | int globalSize = sum; 928 | while(globalSize % 64 != 0) globalSize++; 929 | 930 | // Create lengths image 931 | Image2D * lengths = new Image2D( 932 | ocl.context, 933 | CL_MEM_READ_WRITE, 934 | ImageFormat(CL_R, CL_FLOAT), 935 | sum, sum 936 | ); 937 | ocl.GC->addMemoryObject(lengths); 938 | 939 | // Run linkLengths kernel 940 | Kernel linkLengths(ocl.program, "linkLengths"); 941 | linkLengths.setArg(0, vertices); 942 | linkLengths.setArg(1, *lengths); 943 | ocl.queue.enqueueNDRangeKernel( 944 | linkLengths, 945 | NullRange, 946 | NDRange(sum, sum), 947 | NullRange 948 | ); 949 | 950 | // Create and init compacted_lengths image 951 | float * cl = new float[sum*sum*2](); 952 | Image2D * compacted_lengths = new Image2D( 953 | ocl.context, 954 | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, 955 | ImageFormat(CL_RG, CL_FLOAT), 956 | sum, sum, 957 | 0, 958 | cl 959 | ); 960 | ocl.GC->addMemoryObject(compacted_lengths); 961 | delete[] cl; 962 | 963 | // Create and initialize incs buffer 964 | Buffer incs = Buffer( 965 | ocl.context, 966 | CL_MEM_READ_WRITE, 967 | sizeof(int)*sum 968 | ); 969 | 970 | Kernel initIncsBuffer(ocl.program, "initIntBuffer"); 971 | initIncsBuffer.setArg(0, incs); 972 | ocl.queue.enqueueNDRangeKernel( 973 | initIncsBuffer, 974 | NullRange, 975 | NDRange(sum), 976 | NullRange 977 | ); 978 | 979 | // Run compact kernel 980 | Kernel compactLengths(ocl.program, "compact"); 981 | compactLengths.setArg(0, *lengths); 982 | compactLengths.setArg(1, incs); 983 | compactLengths.setArg(2, *compacted_lengths); 984 | compactLengths.setArg(3, maxDistance); 985 | ocl.queue.enqueueNDRangeKernel( 986 | compactLengths, 987 | NullRange, 988 | NDRange(sum, sum), 989 | NullRange 990 | ); 991 | ocl.queue.finish(); 992 | ocl.GC->deleteMemoryObject(lengths); 993 | 994 | Kernel linkingKernel(ocl.program, "linkCenterpoints"); 995 | linkingKernel.setArg(0, TDF); 996 | linkingKernel.setArg(1, vertices); 997 | linkingKernel.setArg(2, edgeTuples); 998 | linkingKernel.setArg(3, *compacted_lengths); 999 | linkingKernel.setArg(4, sum); 1000 | linkingKernel.setArg(5, Tmean); 1001 | linkingKernel.setArg(6, maxDistance); 1002 | ocl.queue.enqueueNDRangeKernel( 1003 | linkingKernel, 1004 | NullRange, 1005 | NDRange(globalSize), 1006 | NDRange(64) 1007 | ); 1008 | ocl.queue.finish(); 1009 | ocl.GC->deleteMemoryObject(compacted_lengths); 1010 | if(getParamBool(parameters, "timing")) { 1011 | ocl.queue.enqueueMarker(&endEvent); 1012 | ocl.queue.finish(); 1013 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 1014 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 1015 | std::cout << "RUNTIME linking: " << (end-start)*1.0e-6 << " ms" << std::endl; 1016 | } 1017 | 1018 | if(getParamBool(parameters, "timing")) { 1019 | ocl.queue.enqueueMarker(&startEvent); 1020 | } 1021 | 1022 | // Remove duplicate edges 1023 | Image2D edgeTuples2 = Image2D( 1024 | ocl.context, 1025 | CL_MEM_READ_WRITE, 1026 | ImageFormat(CL_R, CL_UNSIGNED_INT8), 1027 | sum, sum 1028 | ); 1029 | Kernel removeDuplicatesKernel(ocl.program, "removeDuplicateEdges"); 1030 | removeDuplicatesKernel.setArg(0, edgeTuples); 1031 | removeDuplicatesKernel.setArg(1, edgeTuples2); 1032 | ocl.queue.enqueueNDRangeKernel( 1033 | removeDuplicatesKernel, 1034 | NullRange, 1035 | NDRange(sum,sum), 1036 | NullRange 1037 | ); 1038 | edgeTuples = edgeTuples2; 1039 | 1040 | // Run HP on edgeTuples 1041 | oul::HistogramPyramid2D hp2(ocl); 1042 | hp2.create(edgeTuples, sum, sum); 1043 | 1044 | std::cout << "number of edges detected " << hp2.getSum() << std::endl; 1045 | if(hp2.getSum() == 0) { 1046 | throw SIPL::SIPLException("No edges were found", __LINE__, __FILE__); 1047 | } else if(hp2.getSum() > 10000000) { 1048 | throw SIPL::SIPLException("More than 10 million edges found. Must be wrong!", __LINE__, __FILE__); 1049 | } else if(hp2.getSum() < 0){ 1050 | throw SIPL::SIPLException("A negative number of edges was found!", __LINE__, __FILE__); 1051 | } 1052 | 1053 | // Run create positions kernel on edges 1054 | Buffer edges = hp2.createPositionBuffer(); 1055 | ocl.queue.finish(); 1056 | hp2.deleteHPlevels(); 1057 | if(getParamBool(parameters, "timing")) { 1058 | ocl.queue.enqueueMarker(&endEvent); 1059 | ocl.queue.finish(); 1060 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 1061 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 1062 | std::cout << "RUNTIME HP creation and traversal: " << (end-start)*1.0e-6 << " ms" << std::endl; 1063 | } 1064 | 1065 | if(getParamBool(parameters, "timing")) { 1066 | ocl.queue.enqueueMarker(&startEvent); 1067 | } 1068 | 1069 | // Do graph component labeling 1070 | Buffer C = Buffer( 1071 | ocl.context, 1072 | CL_MEM_READ_WRITE, 1073 | sizeof(int)*sum 1074 | ); 1075 | Kernel initCBuffer(ocl.program, "initIntBufferID"); 1076 | initCBuffer.setArg(0, C); 1077 | initCBuffer.setArg(1, sum); 1078 | ocl.queue.enqueueNDRangeKernel( 1079 | initCBuffer, 1080 | NullRange, 1081 | NDRange(globalSize), 1082 | NDRange(64) 1083 | ); 1084 | 1085 | 1086 | Buffer m = Buffer( 1087 | ocl.context, 1088 | CL_MEM_WRITE_ONLY, 1089 | sizeof(int) 1090 | ); 1091 | 1092 | Kernel labelingKernel(ocl.program, "graphComponentLabeling"); 1093 | labelingKernel.setArg(0, edges); 1094 | labelingKernel.setArg(1, C); 1095 | labelingKernel.setArg(2, m); 1096 | int M; 1097 | int sum2 = hp2.getSum(); 1098 | labelingKernel.setArg(3, sum2); 1099 | globalSize = sum2; 1100 | while(globalSize % 64 != 0) globalSize++; 1101 | int i = 0; 1102 | int minIterations = 100; 1103 | do { 1104 | // write 0 to m 1105 | if(i > minIterations) { 1106 | M = 0; 1107 | ocl.queue.enqueueWriteBuffer(m, CL_FALSE, 0, sizeof(int), &M); 1108 | } else { 1109 | M = 1; 1110 | } 1111 | 1112 | ocl.queue.enqueueNDRangeKernel( 1113 | labelingKernel, 1114 | NullRange, 1115 | NDRange(globalSize), 1116 | NDRange(64) 1117 | ); 1118 | 1119 | // read m from device 1120 | if(i > minIterations) 1121 | ocl.queue.enqueueReadBuffer(m, CL_TRUE, 0, sizeof(int), &M); 1122 | ++i; 1123 | } while(M == 1); 1124 | std::cout << "did graph component labeling in " << i << " iterations " << std::endl; 1125 | if(getParamBool(parameters, "timing")) { 1126 | ocl.queue.enqueueMarker(&endEvent); 1127 | ocl.queue.finish(); 1128 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 1129 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 1130 | std::cout << "RUNTIME graph component labeling: " << (end-start)*1.0e-6 << " ms" << std::endl; 1131 | } 1132 | 1133 | 1134 | if(getParamBool(parameters, "timing")) { 1135 | ocl.queue.enqueueMarker(&startEvent); 1136 | } 1137 | // Remove small trees 1138 | Buffer S = Buffer( 1139 | ocl.context, 1140 | CL_MEM_READ_WRITE, 1141 | sizeof(int)*sum 1142 | ); 1143 | Kernel initIntBuffer(ocl.program, "initIntBuffer"); 1144 | initIntBuffer.setArg(0, S); 1145 | ocl.queue.enqueueNDRangeKernel( 1146 | initIntBuffer, 1147 | NullRange, 1148 | NDRange(sum), 1149 | NullRange 1150 | ); 1151 | Kernel calculateTreeLengthKernel(ocl.program, "calculateTreeLength"); 1152 | calculateTreeLengthKernel.setArg(0, C); 1153 | calculateTreeLengthKernel.setArg(1, S); 1154 | 1155 | ocl.queue.enqueueNDRangeKernel( 1156 | calculateTreeLengthKernel, 1157 | NullRange, 1158 | NDRange(sum), 1159 | NullRange 1160 | ); 1161 | Image3D centerlines= Image3D( 1162 | ocl.context, 1163 | CL_MEM_READ_WRITE, 1164 | ImageFormat(CL_R, CL_SIGNED_INT8), 1165 | size.x, size.y, size.z 1166 | ); 1167 | 1168 | if(getParamStr(parameters, "centerline-vtk-file") != "off" || 1169 | getParamBool(parameters, "loop-removal")) { 1170 | // Do rasterization of centerline on CPU 1171 | // Transfer edges (size: sum2) and vertices (size: sum) buffers to host 1172 | int * verticesArray = new int[sum*3]; 1173 | int * edgesArray = new int[sum2*2]; 1174 | int * CArray = new int[sum]; 1175 | int * SArray = new int[sum]; 1176 | 1177 | ocl.queue.enqueueReadBuffer(vertices, CL_FALSE, 0, sum*3*sizeof(int), verticesArray); 1178 | ocl.queue.enqueueReadBuffer(edges, CL_FALSE, 0, sum2*2*sizeof(int), edgesArray); 1179 | ocl.queue.enqueueReadBuffer(C, CL_FALSE, 0, sum*sizeof(int), CArray); 1180 | ocl.queue.enqueueReadBuffer(S, CL_FALSE, 0, sum*sizeof(int), SArray); 1181 | 1182 | ocl.queue.finish(); 1183 | float * radiusB = new float[totalSize]; 1184 | ocl.queue.enqueueReadImage(radius, CL_FALSE, offset, region, 0, 0, radiusB); 1185 | std::vector vertices; 1186 | int counter = 0; 1187 | int * indexes = new int[sum]; 1188 | for(int i = 0; i < sum; i++) { 1189 | if(SArray[CArray[i]] >= minTreeLength) { 1190 | int3 v(verticesArray[i*3],verticesArray[i*3+1],verticesArray[i*3+2]); 1191 | vertices.push_back(v); 1192 | indexes[i] = counter; 1193 | counter++; 1194 | } 1195 | } 1196 | std::vector edges; 1197 | int maxEdgeDistance = getParam(parameters, "max-edge-distance"); 1198 | for(int i = 0; i < sum2; i++) { 1199 | if(SArray[CArray[edgesArray[i*2]]] >= minTreeLength && SArray[CArray[edgesArray[i*2+1]]] >= minTreeLength ) { 1200 | // Check length of edge 1201 | int3 A = vertices[indexes[edgesArray[i*2]]]; 1202 | int3 B = vertices[indexes[edgesArray[i*2+1]]]; 1203 | float distance = A.distance(B); 1204 | if(getParamStr(parameters, "centerline-vtk-file") != "off" && 1205 | distance > maxEdgeDistance) { 1206 | float3 direction(B.x-A.x,B.y-A.y,B.z-A.z); 1207 | float3 Af(A.x,A.y,A.z); 1208 | int previous = indexes[edgesArray[i*2]]; 1209 | for(int j = maxEdgeDistance; j < distance; j += maxEdgeDistance) { 1210 | float3 newPos = Af + ((float)j/distance)*direction; 1211 | int3 newVertex(round(newPos.x), round(newPos.y), round(newPos.z)); 1212 | // Create new vertex 1213 | vertices.push_back(newVertex); 1214 | // Add new edge 1215 | SIPL::int2 edge(previous, counter); 1216 | edges.push_back(edge); 1217 | previous = counter; 1218 | counter++; 1219 | } 1220 | // Connect previous vertex to B 1221 | SIPL::int2 edge(previous, indexes[edgesArray[i*2+1]]); 1222 | edges.push_back(edge); 1223 | } else { 1224 | SIPL::int2 v(indexes[edgesArray[i*2]],indexes[edgesArray[i*2+1]]); 1225 | edges.push_back(v); 1226 | } 1227 | } 1228 | } 1229 | 1230 | // Remove loops from graph 1231 | removeLoops(vertices, edges, size); 1232 | 1233 | ocl.queue.finish(); 1234 | char * centerlinesData = createCenterlineVoxels(vertices, edges, radiusB, size); 1235 | ocl.queue.enqueueWriteImage( 1236 | centerlines, 1237 | CL_FALSE, 1238 | offset, 1239 | region, 1240 | 0, 0, 1241 | centerlinesData 1242 | ); 1243 | ocl.queue.enqueueWriteImage( 1244 | radius, 1245 | CL_FALSE, 1246 | offset, 1247 | region, 1248 | 0, 0, 1249 | radiusB 1250 | ); 1251 | 1252 | if(getParamStr(parameters, "centerline-vtk-file") != "off") 1253 | writeToVtkFile(parameters, vertices, edges); 1254 | 1255 | delete[] verticesArray; 1256 | delete[] edgesArray; 1257 | delete[] CArray; 1258 | delete[] SArray; 1259 | delete[] indexes; 1260 | } else { 1261 | // Do rasterization of centerline on GPU 1262 | Kernel RSTKernel(ocl.program, "removeSmallTrees"); 1263 | RSTKernel.setArg(0, edges); 1264 | RSTKernel.setArg(1, vertices); 1265 | RSTKernel.setArg(2, C); 1266 | RSTKernel.setArg(3, S); 1267 | RSTKernel.setArg(4, minTreeLength); 1268 | if(no3Dwrite) { 1269 | Buffer centerlinesBuffer = Buffer( 1270 | ocl.context, 1271 | CL_MEM_WRITE_ONLY, 1272 | sizeof(char)*totalSize 1273 | ); 1274 | 1275 | initCharBuffer.setArg(0, centerlinesBuffer); 1276 | ocl.queue.enqueueNDRangeKernel( 1277 | initCharBuffer, 1278 | NullRange, 1279 | NDRange(totalSize), 1280 | NullRange 1281 | ); 1282 | 1283 | RSTKernel.setArg(5, centerlinesBuffer); 1284 | RSTKernel.setArg(6, size.x); 1285 | RSTKernel.setArg(7, size.y); 1286 | 1287 | ocl.queue.enqueueNDRangeKernel( 1288 | RSTKernel, 1289 | NullRange, 1290 | NDRange(sum2), 1291 | NullRange 1292 | ); 1293 | 1294 | ocl.queue.enqueueCopyBufferToImage( 1295 | centerlinesBuffer, 1296 | centerlines, 1297 | 0, 1298 | offset, 1299 | region 1300 | ); 1301 | 1302 | } else { 1303 | 1304 | Kernel init3DImage(ocl.program, "init3DImage"); 1305 | init3DImage.setArg(0, centerlines); 1306 | ocl.queue.enqueueNDRangeKernel( 1307 | init3DImage, 1308 | NullRange, 1309 | NDRange(size.x, size.y, size.z), 1310 | NullRange 1311 | ); 1312 | 1313 | RSTKernel.setArg(5, centerlines); 1314 | 1315 | ocl.queue.enqueueNDRangeKernel( 1316 | RSTKernel, 1317 | NullRange, 1318 | NDRange(sum2), 1319 | NullRange 1320 | ); 1321 | } 1322 | } 1323 | 1324 | if(getParamBool(parameters, "timing")) { 1325 | ocl.queue.enqueueMarker(&endEvent); 1326 | ocl.queue.finish(); 1327 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 1328 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 1329 | std::cout << "RUNTIME of removing small trees: " << (end-start)*1.0e-6 << " ms" << std::endl; 1330 | } 1331 | return centerlines; 1332 | } 1333 | -------------------------------------------------------------------------------- /parallelCenterlineExtraction.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PCE_H 2 | #define PCE_H 3 | #include "commons.hpp" 4 | #include "SIPL/Types.hpp" 5 | #include "parameters.hpp" 6 | using namespace cl; 7 | 8 | Image3D runNewCenterlineAlg(OpenCL &ocl, SIPL::int3 size, paramList ¶meters, Image3D &vectorField, Image3D &TDF, Image3D &radius); 9 | Image3D runNewCenterlineAlgWithoutOpenCL(OpenCL &ocl, SIPL::int3 size, paramList ¶meters, Image3D &vectorField, Image3D &TDF, Image3D &radius); 10 | #endif 11 | -------------------------------------------------------------------------------- /parameters.cpp: -------------------------------------------------------------------------------- 1 | #include "parameters.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "SIPL/Exceptions.hpp" 6 | #include "tsf-config.h" 7 | #include 8 | #include 9 | #include "tsf-config.h" 10 | using namespace std; 11 | 12 | float stringToFloat(string str) { 13 | float value = 0.0f; 14 | istringstream istr(str); 15 | 16 | istr.imbue(locale("C")); 17 | istr >> value; 18 | return value; 19 | } 20 | 21 | vector split(string str, string delimiter) { 22 | vector list; 23 | int start = 0; 24 | int end = str.find(delimiter); 25 | while(end != str.npos) { 26 | list.push_back(str.substr(start, end-start)); 27 | start = end+1; 28 | end = str.find(delimiter, start); 29 | } 30 | // add last 31 | list.push_back(str.substr(start)); 32 | 33 | return list; 34 | } 35 | 36 | void printAllParameters() { 37 | paramList parameters = initParameters(std::string(PARAMETERS_DIR)); 38 | unordered_map::iterator bIt; 39 | unordered_map::iterator nIt; 40 | unordered_map::iterator sIt; 41 | 42 | printf("%25s \t Default \t Range \t\t\t Description\n", "Name"); 43 | std::cout << "------------------------------------------------------------------------------------------------------" << std::endl; 44 | for(bIt = parameters.bools.begin(); bIt != parameters.bools.end(); ++bIt){ 45 | printf("%25s \t %s \t\t true/false \t\t %s \n", bIt->first.c_str(), bIt->second.get() == 0 ? "false":"true", bIt->second.getDescription().c_str()); 46 | } 47 | 48 | for(nIt = parameters.numerics.begin(); nIt != parameters.numerics.end(); ++nIt){ 49 | if(nIt->second.getStep() >= 1.0f) { 50 | printf("%25s \t %.0f \t\t %.0f-%.0f \t\t %s \n", nIt->first.c_str(), nIt->second.get(), nIt->second.getMin(), nIt->second.getMax(), nIt->second.getDescription().c_str()); 51 | } else { 52 | printf("%25s \t %.3f \t\t %.3f-%.3f \t\t %s \n", nIt->first.c_str(), nIt->second.get(), nIt->second.getMin(), nIt->second.getMax(), nIt->second.getDescription().c_str()); 53 | } 54 | } 55 | for(sIt = parameters.strings.begin(); sIt != parameters.strings.end(); ++sIt){ 56 | std::string possibilitiesString = ""; 57 | std::vector possibilities = sIt->second.getPossibilities(); 58 | std::vector::iterator it; 59 | for(it = possibilities.begin(); it != possibilities.end(); ++it) { 60 | possibilitiesString += *it; 61 | possibilitiesString += " "; 62 | } 63 | printf("%25s \t %s \t\t %s \t\t %s\n", sIt->first.c_str(), sIt->second.get().c_str(), possibilitiesString.c_str(), sIt->second.getDescription().c_str()); 64 | } 65 | } 66 | 67 | void loadParameterPreset(paramList ¶meters, std::string parameter_dir) { 68 | // Check if parameters is set 69 | if(getParamStr(parameters, "parameters") != "none") { 70 | std::string parameterFilename; 71 | if(getParamStr(parameters, "centerline-method") == "gpu") { 72 | parameterFilename = parameter_dir+"/centerline-gpu/" + getParamStr(parameters, "parameters"); 73 | } else if(getParamStr(parameters, "centerline-method") == "test") { 74 | parameterFilename = parameter_dir+"/centerline-test/" + getParamStr(parameters, "parameters"); 75 | } else if(getParamStr(parameters, "centerline-method") == "ridge") { 76 | parameterFilename = parameter_dir+"/centerline-ridge/" + getParamStr(parameters, "parameters"); 77 | } 78 | if(parameterFilename.size() > 0) { 79 | // Load file and parse parameters 80 | std::ifstream file(parameterFilename.c_str()); 81 | if(!file.is_open()) { 82 | //throw SIPL::IOException(parameterFilename.c_str(), __LINE__, __FILE__); // --> malloc 83 | throw SIPL::IOException(parameterFilename.c_str()); 84 | } 85 | 86 | std::string line; 87 | while(!file.eof()) { 88 | getline(file, line); 89 | if(line.size() == 0) 90 | continue; 91 | // split string on the first space 92 | int spacePos = line.find(" "); 93 | if(spacePos != std::string::npos) { 94 | // parameter with value 95 | std::string name = line.substr(0, spacePos); 96 | std::string value = line.substr(spacePos+1); 97 | setParameter(parameters, name, value); 98 | } else { 99 | // parameter with no value 100 | setParameter(parameters, line, "true"); 101 | } 102 | } 103 | file.close(); 104 | } 105 | } 106 | } 107 | 108 | paramList initParameters(std::string parameter_dir) { 109 | paramList parameters; 110 | 111 | std::ifstream file; 112 | // std::string filename = std::string(PARAMETERS_DIR)+"/parameters"; 113 | std::string filename = parameter_dir+"/parameters"; 114 | 115 | file.open(filename.c_str()); 116 | if(!file.is_open()) 117 | throw SIPL::IOException(filename.c_str(), __LINE__, __FILE__); 118 | string line; 119 | getline(file, line); 120 | getline(file, line); // throw away the first comment line 121 | while(file.good()) { 122 | int pos = 0; 123 | pos = line.find(" "); 124 | string name = line.substr(0, pos); 125 | line = line.substr(pos+1); 126 | pos = line.find(" "); 127 | string type = line.substr(0, pos); 128 | line = line.substr(pos+1); 129 | pos = line.find(" "); 130 | string defaultValue = line.substr(0,pos); 131 | 132 | if(type == "bool") { 133 | string description = line.substr(pos+2, line.find("\"", pos+2)-(pos+2)); 134 | string group = line.substr(line.find("\"", pos+2)+2, line.length()-line.find("\"", pos+2)-1); 135 | BoolParameter v = BoolParameter(defaultValue == "true", description, group); 136 | parameters.bools[name] = v; 137 | } else if(type == "num") { 138 | line = line.substr(pos+1); 139 | pos = line.find(" "); 140 | float min = stringToFloat(line.substr(0,pos)); 141 | line = line.substr(pos+1); 142 | pos = line.find(" "); 143 | float max = stringToFloat(line.substr(0,pos)); 144 | line = line.substr(pos+1); 145 | float step = stringToFloat(line); 146 | 147 | int descriptionStart = line.find("\""); 148 | string description = line.substr(descriptionStart+1, line.find("\"", descriptionStart+1)-(descriptionStart+1)); 149 | string group = line.substr(line.find("\"", descriptionStart+1)+2, line.length()-(line.find("\"", descriptionStart+1)+1)); 150 | NumericParameter v = NumericParameter(stringToFloat(defaultValue), min, max, step, description, group); 151 | parameters.numerics[name] = v; 152 | } else if(type == "str") { 153 | vector list ; 154 | int descriptionStart = line.find("\""); 155 | if(descriptionStart-pos > 1) { 156 | list = split(line.substr(pos+1, descriptionStart-(pos+1)), " "); 157 | } 158 | 159 | string description = line.substr(descriptionStart+1, line.find("\"", descriptionStart+1)-(descriptionStart+1)); 160 | string group = line.substr(line.find("\"", descriptionStart+1)+2, line.length()-(line.find("\"", descriptionStart+1)+1)); 161 | StringParameter v = StringParameter(defaultValue, list, description, group); 162 | parameters.strings[name] = v; 163 | } else { 164 | std::string str = "Could not parse parameter of type: " + std::string(type); 165 | throw SIPL::SIPLException(str.c_str()); 166 | } 167 | 168 | getline(file, line); 169 | } 170 | 171 | return parameters; 172 | } 173 | 174 | void setParameter(paramList ¶meters, string name, string value) { 175 | if(parameters.bools.count(name) > 0) { 176 | BoolParameter v = parameters.bools[name]; 177 | bool boolValue = (value == "true") ? true : false; 178 | v.set(boolValue); 179 | parameters.bools[name] = v; 180 | } else if(parameters.numerics.count(name) > 0) { 181 | NumericParameter v = parameters.numerics[name]; 182 | v.set(stringToFloat(value)); 183 | parameters.numerics[name] = v; 184 | } else if(parameters.strings.count(name) > 0) { 185 | StringParameter v = parameters.strings[name]; 186 | if(name == "parameters") { 187 | // Set parameters value without any validation 188 | v.setWithoutValidation(value); 189 | } else { 190 | v.set(value); 191 | } 192 | parameters.strings[name] = v; 193 | } else { 194 | std::string str = "Can not set value for parameter with name: " + name; 195 | throw SIPL::SIPLException(str.c_str()); 196 | } 197 | 198 | } 199 | 200 | float getParam(paramList parameters, string parameterName) { 201 | if(parameters.numerics.count(parameterName) == 0) { 202 | std::string str = "numeric parameter not found: " + parameterName; 203 | throw SIPL::SIPLException(str.c_str()); 204 | } 205 | NumericParameter v = parameters.numerics[parameterName]; 206 | return v.get(); 207 | } 208 | 209 | bool getParamBool(paramList parameters, string parameterName) { 210 | if(parameters.bools.count(parameterName) == 0) { 211 | std::string str = "bool parameter not found: " + parameterName; 212 | throw SIPL::SIPLException(str.c_str()); 213 | } 214 | BoolParameter v = parameters.bools[parameterName]; 215 | return v.get(); 216 | } 217 | 218 | string getParamStr(paramList parameters, string parameterName) { 219 | if(parameters.strings.count(parameterName) == 0) { 220 | std::string str = "string parameter not found: " + parameterName; 221 | throw SIPL::SIPLException(str.c_str()); 222 | } 223 | StringParameter v = parameters.strings[parameterName]; 224 | return v.get(); 225 | } 226 | 227 | paramList getParameters(int argc, char ** argv) { 228 | paramList parameters = initParameters(std::string(PARAMETERS_DIR)); 229 | 230 | // Go through each parameter, first parameter is filename 231 | // Try to see if the parameters parameter and centerline-method is set 232 | for(int i = 2; i < argc; i++) { 233 | string token = argv[i]; 234 | if(token.substr(0,2) == "--") { 235 | // Check to see if the parameter has a value 236 | string nextToken = ""; 237 | if(i+1 < argc) { 238 | nextToken = argv[i+1]; 239 | if(nextToken.substr(0,2) != "--") { 240 | i++; 241 | } 242 | } 243 | if(token.substr(2) == "parameters" || token.substr(2) == "centerline-method") 244 | setParameter(parameters, token.substr(2), nextToken); 245 | } 246 | } 247 | 248 | // If a parameter preset is given load these values 249 | loadParameterPreset(parameters, std::string(PARAMETERS_DIR)); 250 | 251 | // Go through each parameter, first parameter is filename 252 | for(int i = 2; i < argc; i++) { 253 | string token = argv[i]; 254 | if(token.substr(0,2) == "--") { 255 | // Check to see if the parameter has a value 256 | string nextToken = "true"; 257 | if(i+1 < argc) { 258 | nextToken = argv[i+1]; 259 | if(nextToken.substr(0,2) == "--") { 260 | nextToken = "true"; 261 | } else { 262 | i++; 263 | } 264 | } 265 | setParameter(parameters, token.substr(2), nextToken); 266 | } 267 | } 268 | 269 | return parameters; 270 | } 271 | 272 | BoolParameter::BoolParameter(bool defaultValue, string description, string group) { 273 | this->value = defaultValue; 274 | this->description = description; 275 | this->group = group; 276 | } 277 | 278 | bool BoolParameter::get() { 279 | return this->value; 280 | } 281 | 282 | void BoolParameter::set(bool value) { 283 | this->value = value; 284 | } 285 | 286 | NumericParameter::NumericParameter(float defaultValue, float min, float max, float step, string description, string group) { 287 | this->min = min; 288 | this->max = max; 289 | this->step = step; 290 | this->set(defaultValue); 291 | this->description = description; 292 | this->group = group; 293 | } 294 | 295 | float NumericParameter::get() { 296 | return this->value; 297 | } 298 | 299 | void NumericParameter::set(float value) { 300 | if(this->validate(value)) { 301 | this->value = value; 302 | } else { 303 | throw SIPL::SIPLException("Error in setting numerical parameter ", __LINE__, __FILE__); 304 | } 305 | } 306 | 307 | bool NumericParameter::validate(float value) { 308 | return (value >= min) && (value <= max) ;//&& ((float)ceil((value-min)/step) - (float)(value-min)/step < 0.0001); 309 | } 310 | 311 | StringParameter::StringParameter(string defaultValue, vector possibilities, string description, string group) { 312 | this->possibilities = possibilities; 313 | this->set(defaultValue); 314 | this->description = description; 315 | this->group = group; 316 | } 317 | 318 | string StringParameter::get() { 319 | return this->value; 320 | } 321 | 322 | void StringParameter::set(string value) { 323 | if(this->validate(value)) { 324 | this->value = value; 325 | } else { 326 | throw SIPL::SIPLException("Error in setting string parameter", __LINE__, __FILE__); 327 | } 328 | } 329 | 330 | void StringParameter::setWithoutValidation(std::string value) { 331 | this->value = value; 332 | } 333 | 334 | bool StringParameter::validate(string value) { 335 | if(possibilities.size() > 0) { 336 | vector::iterator it; 337 | bool found = false; 338 | for(it=possibilities.begin();it!=possibilities.end();it++){ 339 | if(value == *it) { 340 | found = true; 341 | break; 342 | } 343 | } 344 | return found; 345 | } else { 346 | return true; 347 | } 348 | } 349 | 350 | float NumericParameter::getMax() const { 351 | return max; 352 | } 353 | 354 | void NumericParameter::setMax(float max) { 355 | this->max = max; 356 | } 357 | 358 | float NumericParameter::getMin() const { 359 | return min; 360 | } 361 | 362 | void NumericParameter::setMin(float min) { 363 | this->min = min; 364 | } 365 | 366 | float NumericParameter::getStep() const { 367 | return step; 368 | } 369 | 370 | void NumericParameter::setStep(float step) { 371 | this->step = step; 372 | } 373 | 374 | std::vector StringParameter::getPossibilities() const { 375 | return possibilities; 376 | } 377 | 378 | std::string BoolParameter::getDescription() const { 379 | return description; 380 | } 381 | 382 | std::string NumericParameter::getDescription() const { 383 | return description; 384 | } 385 | 386 | std::string StringParameter::getDescription() const { 387 | return description; 388 | } 389 | 390 | std::string BoolParameter::getGroup() const { 391 | return group; 392 | } 393 | 394 | void BoolParameter::setGroup(std::string group) { 395 | this->group = group; 396 | } 397 | 398 | std::string NumericParameter::getGroup() const { 399 | return group; 400 | } 401 | 402 | void NumericParameter::setGroup(std::string group) { 403 | this->group = group; 404 | } 405 | 406 | std::string StringParameter::getGroup() const { 407 | return group; 408 | } 409 | 410 | 411 | void StringParameter::setGroup(std::string group) { 412 | this->group = group; 413 | } 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | -------------------------------------------------------------------------------- /parameters.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARAMETERS_HPP_ 2 | #define PARAMETERS_HPP_ 3 | 4 | #include 5 | #include 6 | #ifdef CPP11 7 | #include 8 | #include 9 | using std::unordered_map; 10 | using std::tuple; 11 | #else 12 | #include 13 | #include 14 | using boost::unordered_map; 15 | using boost::tuple; 16 | #endif 17 | 18 | 19 | 20 | class BoolParameter { 21 | public: 22 | BoolParameter() {}; 23 | BoolParameter(bool defaultValue, std::string description, std::string group); 24 | bool get(); 25 | void set(bool value); 26 | std::string getDescription() const; 27 | std::string getGroup() const; 28 | void setGroup(std::string group); 29 | private: 30 | bool value; 31 | std::string description; 32 | std::string group; 33 | }; 34 | 35 | class NumericParameter { 36 | public: 37 | NumericParameter() {}; 38 | NumericParameter(float defaultValue, float min, float max, float step, std::string description, std::string group); 39 | float get(); 40 | void set(float value); 41 | bool validate(float value); 42 | float getMax() const; 43 | void setMax(float max); 44 | float getMin() const; 45 | void setMin(float min); 46 | float getStep() const; 47 | void setStep(float step); 48 | std::string getDescription() const; 49 | std::string getGroup() const; 50 | void setGroup(std::string group); 51 | private: 52 | float value; 53 | float min; 54 | float max; 55 | float step; 56 | std::string description; 57 | std::string group; 58 | }; 59 | 60 | class StringParameter { 61 | public: 62 | StringParameter() {}; 63 | StringParameter(std::string defaultValue, std::vector possibilities, std::string description, std::string group); 64 | std::string get(); 65 | void set(std::string value); 66 | void setWithoutValidation(std::string value); 67 | bool validate(std::string value); 68 | std::vector getPossibilities() const; 69 | std::string getDescription() const; 70 | std::string getGroup() const; 71 | void setGroup(std::string group); 72 | private: 73 | std::string value; 74 | std::vector possibilities; 75 | std::string description; 76 | std::string group; 77 | }; 78 | typedef struct paramList { 79 | unordered_map bools; 80 | unordered_map numerics; 81 | unordered_map strings; 82 | } paramList; 83 | 84 | void loadParameterPreset(paramList ¶meters, std::string parameter_dir); 85 | paramList initParameters(std::string parameter_dir); 86 | void setParameter(paramList ¶meters, std::string name, std::string value); 87 | paramList getParameters(int argc, char ** argv); 88 | float getParam(paramList parameters, std::string parameterName); 89 | bool getParamBool(paramList parameters, std::string parameterName); 90 | std::string getParamStr(paramList parameters, std::string parameterName); 91 | void printAllParameters(); 92 | 93 | #endif /* PARAMETERS_HPP_ */ 94 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/AAA-Phantom-CT: -------------------------------------------------------------------------------- 1 | gvf-mu 0.2 2 | gvf-iterations 10 3 | max-distance 50 4 | maximum 1100 5 | minimum 100 6 | mode black 7 | radius-max 90 8 | radius-min 3.5 9 | use-spline-tdf true 10 | use-fmg-gvf true 11 | tdf-high 0.5 12 | min-mean-tdf 0.3 13 | large-blur 0.5 14 | fmax 0.05 15 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/AAA-Vessels-CT: -------------------------------------------------------------------------------- 1 | mode white 2 | minimum 50 3 | maximum 150 4 | fmax 0.3 5 | use-spline-tdf 6 | large-blur 2.0 7 | gvf-iterations 10 8 | gvf-mu 0.2 9 | use-fmg-gvf 10 | radius-min 3.5 11 | radius-max 90 12 | tdf-high 0.5 13 | min-mean-tdf 0.5 14 | max-distance 50 15 | min-tree-length 50 16 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Liver-Vessels-CT: -------------------------------------------------------------------------------- 1 | cropping threshold 2 | cropping-threshold 100 3 | fmax 0.2 4 | gvf-mu 0.05 5 | large-blur 2 6 | max-distance 20 7 | maximum 200 8 | min-scan-lines-threshold 200 9 | min-tree-length 30 10 | minimum 100 11 | radius-max 30 12 | radius-min 1 13 | radius-step 0.5 14 | small-blur 1 15 | use-spline-tdf 16 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Liver-Vessels-MR: -------------------------------------------------------------------------------- 1 | centerline-method gpu 2 | fmax 0.05 3 | gvf-mu 0.05 4 | m-low 0.05 5 | maximum 600 6 | minimum 200 7 | mode black 8 | radius-max 10 9 | radius-min 1 10 | small-blur 0.5 11 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Lung-Airways-CT: -------------------------------------------------------------------------------- 1 | mode black 2 | gvf-iterations 250 3 | min-mean-tdf 0.45 4 | tdf-high 0.45 5 | small-blur 0.5 6 | large-blur 1.0 7 | fmax 0.3 8 | cropping lung 9 | minimum -1024 10 | maximum -400 11 | min-scan-lines-lung 128 12 | min-tree-length 100 13 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Neuro-Vessels-MRA: -------------------------------------------------------------------------------- 1 | mode white 2 | minimum 100 3 | maximum 300 4 | fmax 0.1 5 | max-distance 10 6 | radius-max 8 7 | small-blur 1.0 8 | min-tree-length 5 9 | cube-size 5 10 | cropping threshold 11 | cropping-threshold 150 12 | min-scan-lines-threshold 10 13 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Neuro-Vessels-USA: -------------------------------------------------------------------------------- 1 | minimum 50 2 | maximum 200 3 | fmax 0.1 4 | min-mean-tdf 0.5 5 | radius-min 1.5 6 | radius-max 7.0 7 | small-blur 2.0 8 | large-blur 3.0 9 | cropping threshold 10 | cropping-threshold 50 11 | min-tree-length 10 12 | sphere-segmentation 13 | cube-size 4 14 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Phantom-Acc-US: -------------------------------------------------------------------------------- 1 | mode white 2 | radius-min 5 3 | large-blur 5 4 | fmax 0.05 5 | cropping threshold 6 | cropping-threshold 100 7 | min-scan-lines-threshold 5 8 | minimum 0 9 | maximum 150 10 | no-segmentation 11 | -------------------------------------------------------------------------------- /parameters/centerline-gpu/Synthetic-Vascusynth: -------------------------------------------------------------------------------- 1 | mode white 2 | small-blur 1.0 3 | fmax 0.1 4 | minimum 0 5 | maximum 75 6 | min-mean-tdf 0.3 7 | tdf-high 0.35 8 | cube-size 3 9 | min-tree-length 3 10 | max-distance 10.0 11 | radius-max 4.0 12 | -------------------------------------------------------------------------------- /parameters/centerline-ridge/Lung-Airways-CT: -------------------------------------------------------------------------------- 1 | mode black 2 | min-mean-tdf 0.45 3 | tdf-high 0.45 4 | tdf-low 0.3 5 | m-low 0.2 6 | max-below-tdf-low 0 7 | small-blur 0.5 8 | large-blur 1.0 9 | cropping lung 10 | min-scan-lines-lung 128 11 | minimum -1024 12 | maximum -400 13 | fmax 0.3 14 | min-tree-length 200 15 | min-distance 15 16 | -------------------------------------------------------------------------------- /parameters/centerline-ridge/Neuro-Vessels-MRA: -------------------------------------------------------------------------------- 1 | mode white 2 | minimum 100 3 | maximum 300 4 | fmax 0.1 5 | max-distance 10 6 | radius-max 8 7 | small-blur 1.0 8 | min-tree-length 50 9 | cube-size 5 10 | cropping threshold 11 | cropping-threshold 150 12 | min-scan-lines-threshold 10 13 | -------------------------------------------------------------------------------- /parameters/centerline-ridge/Neuro-Vessels-USA: -------------------------------------------------------------------------------- 1 | minimum 50 2 | maximum 200 3 | fmax 0.1 4 | min-mean-tdf 0.5 5 | radius-min 1.5 6 | radius-max 7.0 7 | small-blur 2.0 8 | large-blur 3.0 9 | cropping threshold 10 | cropping-threshold 50 11 | min-tree-length 50 12 | sphere-segmentation 13 | cube-size 4 14 | -------------------------------------------------------------------------------- /parameters/centerline-ridge/Synthetic-Vascusynth: -------------------------------------------------------------------------------- 1 | mode white 2 | small-blur 1.0 3 | fmax 0.1 4 | minimum 0 5 | maximum 75 6 | m-low 0.05 7 | min-tree-length 5 8 | max-below-tdf-low 0 9 | tdf-high 0.5 10 | tdf-low 0.5 11 | min-mean-tdf 0.5 -------------------------------------------------------------------------------- /parameters/centerline-test/Lung-Airways-CT: -------------------------------------------------------------------------------- 1 | mode black 2 | min-mean-tdf 0.5 3 | small-blur 0.0 4 | large-blur 1.0 5 | cropping lung 6 | minimum -1024 7 | maximum -100 8 | min-tree-length 25 9 | -------------------------------------------------------------------------------- /parameters/centerline-test/Neuro-Vessels-MRA: -------------------------------------------------------------------------------- 1 | mode white 2 | minimum 100 3 | maximum 300 4 | fmax 0.05 5 | max-distance 10 6 | small-blur 1.0 7 | min-tree-length 5 8 | cropping threshold 9 | cropping-threshold 200 10 | min-scan-lines-threshold 10 11 | -------------------------------------------------------------------------------- /parameters/centerline-test/Neuro-Vessels-USA: -------------------------------------------------------------------------------- 1 | minimum 0 2 | maximum 50 3 | radius-min 3.5 4 | large-blur 3.5 5 | cropping threshold 6 | cropping-threshold 50 7 | min-tree-length 10 8 | sphere-segmentation 9 | cube-size 6 10 | fmax 0.1 11 | -------------------------------------------------------------------------------- /parameters/centerline-test/Phantom-Acc-US: -------------------------------------------------------------------------------- 1 | mode white 2 | radius-min 5 3 | large-blur 5 4 | fmax 0.05 5 | cropping threshold 6 | cropping-threshold 100 7 | min-scan-lines-threshold 5 8 | minimum 0 9 | maximum 150 10 | no-segmentation 11 | -------------------------------------------------------------------------------- /parameters/centerline-test/Synthetic-Vascusynth: -------------------------------------------------------------------------------- 1 | mode white 2 | small-blur 1.0 3 | fmax 0.1 4 | minimum 0 5 | maximum 75 6 | min-mean-tdf 0.35 7 | cube-size 3 8 | min-tree-length 5 -------------------------------------------------------------------------------- /parameters/parameters: -------------------------------------------------------------------------------- 1 | # name type default (valid values)/range description group. NB. File should end with newline 2 | device str gpu gpu cpu "Which type of processor to use" general 3 | mode str white black white "Extract black or white tubular structures" general 4 | display bool false "Display results" advanced 5 | centerline-method str gpu test gpu ridge "Centerline extraction method" general 6 | gvf-iterations num 250 0 10000 50 "Number of GVF iterations" gradient-vector-flow 7 | radius-min num 0.5 0.5 50.0 0.5 "Minimum radius of tubular structures" tube-detection-filter 8 | radius-max num 15.0 2.0 300.0 1.0 "Maximum radius of tubular structures" tube-detection-filter 9 | radius-step num 1.0 0.0 5.0 0.5 "Step size of radius" tube-detection-filter 10 | fmax num 0.2 0.01 0.9 0.01 "Maximum gradient length (for contrast invariance)" general 11 | gvf-mu num 0.05 0.0 0.5 0.01 "Mu regularization constant of GVF" gradient-vector-flow 12 | small-blur num 0.0 0.0 5.0 0.5 "Std. Dev. of Gaussian blur for small tubular structures" general 13 | large-blur num 1.0 0.0 15.0 0.5 "Std. Dev. of Gaussian blur for large tubular structures" general 14 | tdf-high num 0.5 0.1 1.0 0.1 "TDF response threshold" centerline-general 15 | min-distance num 5 0 100 1 "Minimum length for accepting centerlines (ridge traversal)" centerline-ridge 16 | m-low num 0.05 0.0 0.2 0.01 "Threshold of vector field magnitude (ridge traversal)" centerline-ridge 17 | tdf-low num 0.5 0.0 1.0 0.1 "TDF response lower threshold (ridge traversal)" centerline-ridge 18 | max-below-tdf-low num 0 0 5 1 "Number of allowed voxels below TDF lower threshold (ridge traversal)" centerline-ridge 19 | min-mean-tdf num 0.5 0.0 1.0 0.01 "Minimum mean TDF response along centerline" centerline-general 20 | min-tree-length num 5 0 1000 5 "Minimum centerline tree length" centerline-general 21 | timing bool false "Timing of application" advanced 22 | cube-size num 4 0 10 1 "Grid size of (parallel centerline extraction)" centerline-gpu 23 | max-distance num 25.0 0.0 50.0 1.0 "Max distance for connecting two centerpoints (parallel centerline extraction)" centerline-gpu 24 | centerpoints-only bool false "Extract centerpoints only (parallel centerline extraction)" centerline-gpu 25 | tdf-only bool false "Generate TDF response only" tube-detection-filter 26 | no-segmentation bool false "Don't perform segmentation" general 27 | centerline-vtk-file str off "Filepath to centerline VTK file (ommit to skip)" storage 28 | sphere-segmentation bool false "Do a simple sphere segmentation" general 29 | minimum str off "Minimum intensity value" general 30 | maximum str off "Maximum intensity value" general 31 | cropping str no no lung threshold "Cropping method" cropping 32 | min-scan-lines-threshold num 10 0 1024 1 "Minimum nr. of scan lines (threshold cropping)" cropping 33 | min-scan-lines-lung num 200 0 1024 1 "Minimum nr. of scan lines (lung cropping)" cropping 34 | cropping-threshold num 0 0 3000 10 "Cropping threshold" cropping 35 | cropping-start-z str end end middle "Where to start cropping in the z direction" cropping 36 | buffers-only bool false "Use OpenCL buffers instead of 3D textures" advanced 37 | storage-dir str off "Directory of where to store results (ommit to skip)" storage 38 | storage-name str unnamed "Storage name" storage 39 | 32bit-vectors bool false "Force the use of 32 bit vectors" advanced 40 | 16bit-vectors bool true "Force the use of 16 bit vectors" advanced 41 | parameters str none none AAA-Vessels-CT Liver-Vessels-CT Liver-Vessels-MR Lung-Airways-CT Neuro-Vessels-USA Neuro-Vessels-MRA Phantom-Acc-US Synthetic-Vascusynth "Which parameter preset to use" preset 42 | loop-removal bool true "Perform loop removal on centerlines on CPU" advanced 43 | timer-total bool false "Measure the total execution time" advanced 44 | max-edge-distance num 3 2 30 1 "Maxium distance between two vertices in the vtk centerline file. If an edge has a length above it, more vertices and edges will be created in between" centerline-gpu 45 | use-spline-tdf bool false "Use Spline TDF" tube-detection-filter 46 | use-fmg-gvf bool false "Use FMG GVF" gradient-vector-flow 47 | -------------------------------------------------------------------------------- /ridgeTraversalCenterlineExtraction.cpp: -------------------------------------------------------------------------------- 1 | #include "ridgeTraversalCenterlineExtraction.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "eigenanalysisOfHessian.hpp" 6 | #include "timing.hpp" 7 | #ifdef CPP11 8 | #include 9 | using std::unordered_set; 10 | #else 11 | #include 12 | using boost::unordered_set; 13 | #endif 14 | 15 | typedef struct point { 16 | float value; 17 | int x,y,z; 18 | } point; 19 | 20 | class PointComparison { 21 | public: 22 | bool operator() (const point &lhs, const point &rhs) const { 23 | return (lhs.value < rhs.value); 24 | } 25 | }; 26 | 27 | float sign(float a) { 28 | return a < 0 ? -1.0f: 1.0f; 29 | } 30 | 31 | #define LPOS(a,b,c) (a)+(b)*(size.x)+(c)*(size.x*size.y) 32 | #define POS(pos) pos.x+pos.y*size.x+pos.z*size.x*size.y 33 | #define M(a,b,c) 1-sqrt(pow(T.Fx[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fy[a+b*size.x+c*size.x*size.y],2.0f) + pow(T.Fz[a+b*size.x+c*size.x*size.y],2.0f)) 34 | #define SQR_MAG(pos) sqrt(pow(T.Fx[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fy[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.Fz[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 35 | #define SQR_MAG_SMALL(pos) sqrt(pow(T.FxSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FySmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f) + pow(T.FzSmall[pos.x+pos.y*size.x+pos.z*size.x*size.y],2.0f)) 36 | 37 | char * runRidgeTraversal(TubeSegmentation &T, SIPL::int3 size, paramList ¶meters, std::stack centerlineStack) { 38 | 39 | float Thigh = getParam(parameters, "tdf-high"); // 0.6 40 | int Dmin = getParam(parameters, "min-distance"); 41 | float Mlow = getParam(parameters, "m-low"); // 0.2 42 | float Tlow = getParam(parameters, "tdf-low"); // 0.4 43 | int maxBelowTlow = getParam(parameters, "max-below-tdf-low"); // 2 44 | float minMeanTube = getParam(parameters, "min-mean-tdf"); //0.6 45 | int TreeMin = getParam(parameters, "min-tree-length"); // 200 46 | const int totalSize = size.x*size.y*size.z; 47 | 48 | int * centerlines = new int[totalSize](); 49 | INIT_TIMER 50 | 51 | // Create queue 52 | std::priority_queue, PointComparison> queue; 53 | 54 | START_TIMER 55 | // Collect all valid start points 56 | #pragma omp parallel for 57 | for(int z = 2; z < size.z-2; z++) { 58 | for(int y = 2; y < size.y-2; y++) { 59 | for(int x = 2; x < size.x-2; x++) { 60 | if(T.TDF[LPOS(x,y,z)] < Thigh) 61 | continue; 62 | 63 | int3 pos(x,y,z); 64 | bool valid = true; 65 | for(int a = -1; a < 2; a++) { 66 | for(int b = -1; b < 2; b++) { 67 | for(int c = -1; c < 2; c++) { 68 | int3 nPos(x+a,y+b,z+c); 69 | if(SQR_MAG(nPos) < SQR_MAG(pos)) { 70 | valid = false; 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | 77 | if(valid) { 78 | point p; 79 | p.value = T.TDF[LPOS(x,y,z)]; 80 | p.x = x; 81 | p.y = y; 82 | p.z = z; 83 | #pragma omp critical 84 | queue.push(p); 85 | } 86 | } 87 | } 88 | } 89 | 90 | std::cout << "Processing " << queue.size() << " valid start points" << std::endl; 91 | if(queue.size() == 0) { 92 | throw SIPL::SIPLException("no valid start points found", __LINE__, __FILE__); 93 | } 94 | STOP_TIMER("finding start points") 95 | START_TIMER 96 | int counter = 1; 97 | T.TDF[0] = 0; 98 | T.Fx[0] = 1; 99 | T.Fy[0] = 0; 100 | T.Fz[0] = 0; 101 | 102 | 103 | // Create a map of centerline distances 104 | unordered_map centerlineDistances; 105 | 106 | // Create a map of centerline stacks 107 | unordered_map > centerlineStacks; 108 | 109 | while(!queue.empty()) { 110 | // Traverse from new start point 111 | point p = queue.top(); 112 | queue.pop(); 113 | 114 | // Has it been handled before? 115 | if(centerlines[LPOS(p.x,p.y,p.z)] == 1) 116 | continue; 117 | 118 | unordered_set newCenterlines; 119 | newCenterlines.insert(LPOS(p.x,p.y,p.z)); 120 | int distance = 1; 121 | int connections = 0; 122 | int prevConnection = -1; 123 | int secondConnection = -1; 124 | float meanTube = T.TDF[LPOS(p.x,p.y,p.z)]; 125 | 126 | // Create new stack for this centerline 127 | std::stack stack; 128 | CenterlinePoint startPoint; 129 | startPoint.pos.x = p.x; 130 | startPoint.pos.y = p.y; 131 | startPoint.pos.z = p.z; 132 | 133 | stack.push(startPoint); 134 | 135 | // For each direction 136 | for(int direction = -1; direction < 3; direction += 2) { 137 | int belowTlow = 0; 138 | int3 position(p.x,p.y,p.z); 139 | float3 t_i = getTubeDirection(T, position, size); 140 | t_i.x *= direction; 141 | t_i.y *= direction; 142 | t_i.z *= direction; 143 | float3 t_i_1; 144 | t_i_1.x = t_i.x; 145 | t_i_1.y = t_i.y; 146 | t_i_1.z = t_i.z; 147 | 148 | 149 | // Traverse 150 | while(true) { 151 | int3 maxPoint(0,0,0); 152 | 153 | // Check for out of bounds 154 | if(position.x < 3 || position.x > size.x-3 || position.y < 3 || position.y > size.y-3 || position.z < 3 || position.z > size.z-3) 155 | break; 156 | 157 | // Try to find next point from all neighbors 158 | for(int a = -1; a < 2; a++) { 159 | for(int b = -1; b < 2; b++) { 160 | for(int c = -1; c < 2; c++) { 161 | int3 n(position.x+a,position.y+b,position.z+c); 162 | if((a == 0 && b == 0 && c == 0) || T.TDF[POS(n)] == 0.0f) 163 | continue; 164 | 165 | float3 dir((float)(n.x-position.x),(float)(n.y-position.y),(float)(n.z-position.z)); 166 | dir = dir.normalize(); 167 | if( (dir.x*t_i.x+dir.y*t_i.y+dir.z*t_i.z) <= 0.1) 168 | continue; 169 | 170 | if(T.radius[POS(n)] >= 1.5f) { 171 | if(M(n.x,n.y,n.z) > M(maxPoint.x,maxPoint.y,maxPoint.z)) 172 | maxPoint = n; 173 | } else { 174 | if(T.TDF[LPOS(n.x,n.y,n.z)]*M(n.x,n.y,n.z) > T.TDF[POS(maxPoint)]*M(maxPoint.x,maxPoint.y,maxPoint.z)) 175 | maxPoint = n; 176 | } 177 | 178 | } 179 | } 180 | } 181 | 182 | if(maxPoint.x+maxPoint.y+maxPoint.z > 0) { 183 | // New maxpoint found, check it! 184 | if(centerlines[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)] > 0) { 185 | // Hit an existing centerline 186 | if(prevConnection == -1) { 187 | prevConnection = centerlines[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)]; 188 | } else { 189 | if(prevConnection ==centerlines[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)]) { 190 | // A loop has occured, reject this centerline 191 | connections = 5; 192 | } else { 193 | secondConnection = centerlines[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)]; 194 | } 195 | } 196 | break; 197 | } else if(M(maxPoint.x,maxPoint.y,maxPoint.z) < Mlow || (belowTlow > maxBelowTlow && T.TDF[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)] < Tlow)) { 198 | // New point is below thresholds 199 | break; 200 | } else if(newCenterlines.count(LPOS(maxPoint.x,maxPoint.y,maxPoint.z)) > 0) { 201 | // Loop detected! 202 | break; 203 | } else { 204 | // Point is OK, proceed to add it and continue 205 | if(T.TDF[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)] < Tlow) { 206 | belowTlow++; 207 | } else { 208 | belowTlow = 0; 209 | } 210 | 211 | // Update direction 212 | //float3 e1 = getTubeDirection(T, maxPoint,size.x,size.y,size.z); 213 | 214 | //TODO: check if all eigenvalues are negative, if so find the egeinvector that best matches 215 | float3 lambda, e1, e2, e3; 216 | doEigen(T, maxPoint, size, &lambda, &e1, &e2, &e3); 217 | if((lambda.x < 0 && lambda.y < 0 && lambda.z < 0)) { 218 | if(fabs(t_i.dot(e3)) > fabs(t_i.dot(e2))) { 219 | if(fabs(t_i.dot(e3)) > fabs(t_i.dot(e1))) { 220 | e1 = e3; 221 | } 222 | } else if(fabs(t_i.dot(e2)) > fabs(t_i.dot(e1))) { 223 | e1 = e2; 224 | } 225 | } 226 | 227 | 228 | float maintain_dir = sign(e1.dot(t_i)); 229 | float3 vec_sum; 230 | vec_sum.x = maintain_dir*e1.x + t_i.x + t_i_1.x; 231 | vec_sum.y = maintain_dir*e1.y + t_i.y + t_i_1.y; 232 | vec_sum.z = maintain_dir*e1.z + t_i.z + t_i_1.z; 233 | vec_sum = vec_sum.normalize(); 234 | t_i_1 = t_i; 235 | t_i = vec_sum; 236 | 237 | // update position 238 | position = maxPoint; 239 | distance ++; 240 | newCenterlines.insert(LPOS(maxPoint.x,maxPoint.y,maxPoint.z)); 241 | meanTube += T.TDF[LPOS(maxPoint.x,maxPoint.y,maxPoint.z)]; 242 | 243 | // Create centerline point 244 | CenterlinePoint p; 245 | p.pos = position; 246 | p.next = &(stack.top()); // add previous 247 | if(T.radius[POS(p.pos)] > 3.0f) { 248 | p.large = true; 249 | } else { 250 | p.large = false; 251 | } 252 | 253 | // Add point to stack 254 | stack.push(p); 255 | } 256 | } else { 257 | // No maxpoint found, stop! 258 | break; 259 | } 260 | 261 | } // End traversal 262 | } // End for each direction 263 | 264 | // Check to see if new traversal can be added 265 | //std::cout << "Finished. Distance " << distance << " meanTube: " << meanTube/distance << std::endl; 266 | if(distance > Dmin && meanTube/distance > minMeanTube && connections < 2) { 267 | //std::cout << "Finished. Distance " << distance << " meanTube: " << meanTube/distance << std::endl; 268 | //std::cout << "------------------- New centerlines added #" << counter << " -------------------------" << std::endl; 269 | 270 | unordered_set::iterator usit; 271 | if(prevConnection == -1) { 272 | // No connections 273 | for(usit = newCenterlines.begin(); usit != newCenterlines.end(); usit++) { 274 | centerlines[*usit] = counter; 275 | } 276 | centerlineDistances[counter] = distance; 277 | centerlineStacks[counter] = stack; 278 | counter ++; 279 | } else { 280 | // The first connection 281 | 282 | std::stack prevConnectionStack = centerlineStacks[prevConnection]; 283 | while(!stack.empty()) { 284 | prevConnectionStack.push(stack.top()); 285 | stack.pop(); 286 | } 287 | 288 | for(usit = newCenterlines.begin(); usit != newCenterlines.end(); usit++) { 289 | centerlines[*usit] = prevConnection; 290 | } 291 | centerlineDistances[prevConnection] += distance; 292 | if(secondConnection != -1) { 293 | // Two connections, move secondConnection to prevConnection 294 | std::stack secondConnectionStack = centerlineStacks[secondConnection]; 295 | centerlineStacks.erase(secondConnection); 296 | while(!secondConnectionStack.empty()) { 297 | prevConnectionStack.push(secondConnectionStack.top()); 298 | secondConnectionStack.pop(); 299 | } 300 | 301 | #pragma omp parallel for 302 | for(int i = 0; i < totalSize;i++) { 303 | if(centerlines[i] == secondConnection) 304 | centerlines[i] = prevConnection; 305 | } 306 | centerlineDistances[prevConnection] += centerlineDistances[secondConnection]; 307 | centerlineDistances.erase(secondConnection); 308 | } 309 | 310 | centerlineStacks[prevConnection] = prevConnectionStack; 311 | } 312 | } // end if new point can be added 313 | } // End while queue is not empty 314 | std::cout << "Finished traversal" << std::endl; 315 | STOP_TIMER("traversal") 316 | START_TIMER 317 | 318 | if(centerlineDistances.size() == 0) { 319 | //throw SIPL::SIPLException("no centerlines were extracted"); 320 | char * returnCenterlines = new char[totalSize](); 321 | return returnCenterlines; 322 | } 323 | 324 | // Find largest connected tree and all trees above a certain size 325 | unordered_map::iterator it; 326 | int max = centerlineDistances.begin()->first; 327 | std::list trees; 328 | for(it = centerlineDistances.begin(); it != centerlineDistances.end(); it++) { 329 | if(it->second > centerlineDistances[max]) 330 | max = it->first; 331 | if(it->second > TreeMin) 332 | trees.push_back(it->first); 333 | } 334 | std::list::iterator it2; 335 | // TODO: if use the method with TreeMin have to add them to centerlineStack also 336 | centerlineStack = centerlineStacks[max]; 337 | for(it2 = trees.begin(); it2 != trees.end(); it2++) { 338 | while(!centerlineStacks[*it2].empty()) { 339 | centerlineStack.push(centerlineStacks[*it2].top()); 340 | centerlineStacks[*it2].pop(); 341 | } 342 | } 343 | 344 | char * returnCenterlines = new char[totalSize](); 345 | // Mark largest tree with 1, and rest with 0 346 | #pragma omp parallel for 347 | for(int i = 0; i < totalSize;i++) { 348 | if(centerlines[i] == max) { 349 | //if(centerlines[i] > 0) { 350 | returnCenterlines[i] = 1; 351 | } else { 352 | bool valid = false; 353 | for(it2 = trees.begin(); it2 != trees.end(); it2++) { 354 | if(centerlines[i] == *it2) { 355 | returnCenterlines[i] = 1; 356 | valid = true; 357 | break; 358 | } 359 | } 360 | if(!valid) 361 | returnCenterlines[i] = 0; 362 | 363 | } 364 | } 365 | STOP_TIMER("finding largest tree") 366 | 367 | delete[] centerlines; 368 | return returnCenterlines; 369 | } 370 | -------------------------------------------------------------------------------- /ridgeTraversalCenterlineExtraction.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RIDGE_TRAVERSAL_HPP 2 | #define RIDGE_TRAVERSAL_HPP 3 | 4 | #include "parameters.hpp" 5 | #include "tube-segmentation.hpp" 6 | #include "SIPL/Types.hpp" 7 | #include 8 | 9 | typedef struct CenterlinePoint { 10 | SIPL::int3 pos; 11 | bool large; 12 | CenterlinePoint * next; 13 | } CenterlinePoint; 14 | 15 | char * runRidgeTraversal(TubeSegmentation &T, SIPL::int3 size, paramList ¶meters, std::stack centerlineStack); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /segmentation.cpp: -------------------------------------------------------------------------------- 1 | #include "segmentation.hpp" 2 | #include 3 | using namespace cl; 4 | 5 | Image3D runInverseGradientSegmentation(OpenCL &ocl, Image3D ¢erline, Image3D &vectorField, Image3D &radius, SIPL::int3 size, paramList parameters) { 6 | const int totalSize = size.x*size.y*size.z; 7 | const bool no3Dwrite = !getParamBool(parameters, "3d_write"); 8 | cl::Event startEvent, endEvent; 9 | cl_ulong start, end; 10 | if(getParamBool(parameters, "timing")) { 11 | ocl.queue.enqueueMarker(&startEvent); 12 | } 13 | 14 | Kernel dilateKernel = Kernel(ocl.program, "dilate"); 15 | Kernel erodeKernel = Kernel(ocl.program, "erode"); 16 | Kernel initGrowKernel = Kernel(ocl.program, "initGrowing"); 17 | Kernel growKernel = Kernel(ocl.program, "grow"); 18 | 19 | cl::size_t<3> offset; 20 | offset[0] = 0; 21 | offset[1] = 0; 22 | offset[2] = 0; 23 | cl::size_t<3> region; 24 | region[0] = size.x; 25 | region[1] = size.y; 26 | region[2] = size.z; 27 | 28 | 29 | Image3D volume = Image3D(ocl.context, CL_MEM_READ_WRITE, ImageFormat(CL_R, CL_SIGNED_INT8), size.x, size.y, size.z); 30 | ocl.queue.enqueueCopyImage(centerline, volume, offset, offset, region); 31 | 32 | int stopGrowing = 0; 33 | Buffer stop = Buffer(ocl.context, CL_MEM_WRITE_ONLY, sizeof(int)); 34 | ocl.queue.enqueueWriteBuffer(stop, CL_FALSE, 0, sizeof(int), &stopGrowing); 35 | 36 | growKernel.setArg(1, vectorField); 37 | growKernel.setArg(3, stop); 38 | 39 | int i = 0; 40 | int minimumIterations = 0; 41 | if(no3Dwrite) { 42 | Buffer volume2 = Buffer( 43 | ocl.context, 44 | CL_MEM_READ_WRITE, 45 | sizeof(char)*totalSize 46 | ); 47 | ocl.queue.enqueueCopyImageToBuffer( 48 | volume, 49 | volume2, 50 | offset, 51 | region, 52 | 0 53 | ); 54 | initGrowKernel.setArg(0, volume); 55 | initGrowKernel.setArg(1, volume2); 56 | initGrowKernel.setArg(2, radius); 57 | ocl.queue.enqueueNDRangeKernel( 58 | initGrowKernel, 59 | NullRange, 60 | NDRange(size.x, size.y, size.z), 61 | NullRange 62 | ); 63 | ocl.queue.enqueueCopyBufferToImage( 64 | volume2, 65 | volume, 66 | 0, 67 | offset, 68 | region 69 | ); 70 | growKernel.setArg(0, volume); 71 | growKernel.setArg(2, volume2); 72 | while(stopGrowing == 0) { 73 | if(i > minimumIterations) { 74 | stopGrowing = 1; 75 | ocl.queue.enqueueWriteBuffer(stop, CL_TRUE, 0, sizeof(int), &stopGrowing); 76 | } 77 | 78 | ocl.queue.enqueueNDRangeKernel( 79 | growKernel, 80 | NullRange, 81 | NDRange(size.x, size.y, size.z), 82 | NullRange 83 | ); 84 | if(i > minimumIterations) 85 | ocl.queue.enqueueReadBuffer(stop, CL_TRUE, 0, sizeof(int), &stopGrowing); 86 | i++; 87 | ocl.queue.enqueueCopyBufferToImage( 88 | volume2, 89 | volume, 90 | 0, 91 | offset, 92 | region 93 | ); 94 | } 95 | 96 | } else { 97 | Image3D volume2 = Image3D(ocl.context, CL_MEM_READ_WRITE, ImageFormat(CL_R, CL_SIGNED_INT8), size.x, size.y, size.z); 98 | ocl.queue.enqueueCopyImage(volume, volume2, offset, offset, region); 99 | initGrowKernel.setArg(0, volume); 100 | initGrowKernel.setArg(1, volume2); 101 | initGrowKernel.setArg(2, radius); 102 | ocl.queue.enqueueNDRangeKernel( 103 | initGrowKernel, 104 | NullRange, 105 | NDRange(size.x, size.y, size.z), 106 | NDRange(4,4,4) 107 | ); 108 | while(stopGrowing == 0) { 109 | if(i > minimumIterations) { 110 | stopGrowing = 1; 111 | ocl.queue.enqueueWriteBuffer(stop, CL_FALSE, 0, sizeof(int), &stopGrowing); 112 | } 113 | if(i % 2 == 0) { 114 | growKernel.setArg(0, volume); 115 | growKernel.setArg(2, volume2); 116 | } else { 117 | growKernel.setArg(0, volume2); 118 | growKernel.setArg(2, volume); 119 | } 120 | 121 | ocl.queue.enqueueNDRangeKernel( 122 | growKernel, 123 | NullRange, 124 | NDRange(size.x, size.y, size.z), 125 | NDRange(4,4,4) 126 | ); 127 | if(i > minimumIterations) 128 | ocl.queue.enqueueReadBuffer(stop, CL_TRUE, 0, sizeof(int), &stopGrowing); 129 | i++; 130 | } 131 | 132 | } 133 | 134 | std::cout << "segmentation result grown in " << i << " iterations" << std::endl; 135 | 136 | if(no3Dwrite) { 137 | Buffer volumeBuffer = Buffer( 138 | ocl.context, 139 | CL_MEM_WRITE_ONLY, 140 | sizeof(char)*totalSize 141 | ); 142 | dilateKernel.setArg(0, volume); 143 | dilateKernel.setArg(1, volumeBuffer); 144 | 145 | ocl.queue.enqueueNDRangeKernel( 146 | dilateKernel, 147 | NullRange, 148 | NDRange(size.x, size.y, size.z), 149 | NullRange 150 | ); 151 | 152 | ocl.queue.enqueueCopyBufferToImage( 153 | volumeBuffer, 154 | volume, 155 | 0, 156 | offset, 157 | region); 158 | 159 | erodeKernel.setArg(0, volume); 160 | erodeKernel.setArg(1, volumeBuffer); 161 | 162 | ocl.queue.enqueueNDRangeKernel( 163 | erodeKernel, 164 | NullRange, 165 | NDRange(size.x, size.y, size.z), 166 | NullRange 167 | ); 168 | ocl.queue.enqueueCopyBufferToImage( 169 | volumeBuffer, 170 | volume, 171 | 0, 172 | offset, 173 | region 174 | ); 175 | } else { 176 | Image3D volume2 = Image3D( 177 | ocl.context, 178 | CL_MEM_READ_WRITE, 179 | ImageFormat(CL_R, CL_SIGNED_INT8), 180 | size.x, size.y, size.z 181 | ); 182 | 183 | Kernel init3DImage(ocl.program, "init3DImage"); 184 | init3DImage.setArg(0, volume2); 185 | ocl.queue.enqueueNDRangeKernel( 186 | init3DImage, 187 | NullRange, 188 | NDRange(size.x, size.y, size.z), 189 | NullRange 190 | ); 191 | 192 | dilateKernel.setArg(0, volume); 193 | dilateKernel.setArg(1, volume2); 194 | 195 | ocl.queue.enqueueNDRangeKernel( 196 | dilateKernel, 197 | NullRange, 198 | NDRange(size.x, size.y, size.z), 199 | NullRange 200 | ); 201 | 202 | erodeKernel.setArg(0, volume2); 203 | erodeKernel.setArg(1, volume); 204 | 205 | ocl.queue.enqueueNDRangeKernel( 206 | erodeKernel, 207 | NullRange, 208 | NDRange(size.x, size.y, size.z), 209 | NullRange 210 | ); 211 | } 212 | if(getParamBool(parameters, "timing")) { 213 | ocl.queue.enqueueMarker(&endEvent); 214 | ocl.queue.finish(); 215 | startEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &start); 216 | endEvent.getProfilingInfo(CL_PROFILING_COMMAND_START, &end); 217 | std::cout << "RUNTIME of segmentation: " << (end-start)*1.0e-6 << " ms" << std::endl; 218 | } 219 | 220 | return volume; 221 | } 222 | 223 | Image3D runSphereSegmentation(OpenCL ocl, Image3D ¢erline, Image3D &radius, SIPL::int3 size, paramList parameters) { 224 | const bool no3Dwrite = !getParamBool(parameters, "3d_write"); 225 | if(no3Dwrite) { 226 | cl::size_t<3> offset; 227 | offset[0] = 0; 228 | offset[1] = 0; 229 | offset[2] = 0; 230 | cl::size_t<3> region; 231 | region[0] = size.x; 232 | region[1] = size.y; 233 | region[2] = size.z; 234 | 235 | const int totalSize = size.x*size.y*size.z; 236 | Buffer segmentation = Buffer( 237 | ocl.context, 238 | CL_MEM_WRITE_ONLY, 239 | sizeof(char)*totalSize 240 | ); 241 | Kernel initKernel = Kernel(ocl.program, "initCharBuffer"); 242 | initKernel.setArg(0, segmentation); 243 | ocl.queue.enqueueNDRangeKernel( 244 | initKernel, 245 | NullRange, 246 | NDRange(totalSize), 247 | NDRange(4*4*4) 248 | ); 249 | 250 | Kernel kernel = Kernel(ocl.program, "sphereSegmentation"); 251 | kernel.setArg(0, centerline); 252 | kernel.setArg(1, radius); 253 | kernel.setArg(2, segmentation); 254 | ocl.queue.enqueueNDRangeKernel( 255 | kernel, 256 | NullRange, 257 | NDRange(size.x, size.y, size.z), 258 | NDRange(4,4,4) 259 | ); 260 | 261 | Image3D segmentationImage = Image3D( 262 | ocl.context, 263 | CL_MEM_WRITE_ONLY, 264 | ImageFormat(CL_R, CL_UNSIGNED_INT8), 265 | size.x, size.y, size.z 266 | ); 267 | 268 | ocl.queue.enqueueCopyBufferToImage( 269 | segmentation, 270 | segmentationImage, 271 | 0, 272 | offset, 273 | region 274 | ); 275 | 276 | return segmentationImage; 277 | } else { 278 | Image3D segmentation = Image3D( 279 | ocl.context, 280 | CL_MEM_WRITE_ONLY, 281 | ImageFormat(CL_R, CL_UNSIGNED_INT8), 282 | size.x, size.y, size.z 283 | ); 284 | Kernel initKernel = Kernel(ocl.program, "init3DImage"); 285 | initKernel.setArg(0, segmentation); 286 | ocl.queue.enqueueNDRangeKernel( 287 | initKernel, 288 | NullRange, 289 | NDRange(size.x, size.y, size.z), 290 | NDRange(4,4,4) 291 | ); 292 | 293 | Kernel kernel = Kernel(ocl.program, "sphereSegmentation"); 294 | kernel.setArg(0, centerline); 295 | kernel.setArg(1, radius); 296 | kernel.setArg(2, segmentation); 297 | ocl.queue.enqueueNDRangeKernel( 298 | kernel, 299 | NullRange, 300 | NDRange(size.x, size.y, size.z), 301 | NDRange(4,4,4) 302 | ); 303 | 304 | return segmentation; 305 | } 306 | 307 | } 308 | 309 | -------------------------------------------------------------------------------- /segmentation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SEGMENTATION_H 2 | #define SEGMENTATION_H 3 | 4 | #include "commons.hpp" 5 | #include "parameters.hpp" 6 | using namespace cl; 7 | 8 | Image3D runInverseGradientSegmentation(OpenCL &ocl, Image3D ¢erline, Image3D &vectorField, Image3D &radius, SIPL::int3 size, paramList parameters); 9 | 10 | Image3D runSphereSegmentation(OpenCL ocl, Image3D ¢erline, Image3D &radius, SIPL::int3 size, paramList parameters); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | # Locate GTest 4 | find_package(GTest REQUIRED) 5 | include_directories(${GTEST_INCLUDE_DIRS}) 6 | 7 | # Link runTests with what we want to test and the GTest and pthread library 8 | add_executable(runTests tests.cpp) 9 | target_link_libraries(runTests tubeSegmentationLib ${GTEST_LIBRARIES} pthread) 10 | 11 | add_test(runTests runTests) -------------------------------------------------------------------------------- /tests/TSFOutputTests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.hpp" 2 | 3 | TEST(TSFOutputTest, Initialization) { 4 | TSFOutput output(oul::DeviceCriteria(), new SIPL::int3); 5 | EXPECT_FALSE(output.hasSegmentation()); 6 | EXPECT_FALSE(output.hasCenterlineVoxels()); 7 | EXPECT_FALSE(output.hasTDF()); 8 | } 9 | 10 | TEST(TSFOutputTest, SetHostData) { 11 | TSFOutput output(oul::DeviceCriteria(), new SIPL::int3); 12 | output.setTDF(new float); 13 | EXPECT_TRUE(output.hasTDF()); 14 | EXPECT_FALSE(output.hasSegmentation()); 15 | EXPECT_FALSE(output.hasCenterlineVoxels()); 16 | output.setSegmentation(new char); 17 | EXPECT_TRUE(output.hasTDF()); 18 | EXPECT_TRUE(output.hasSegmentation()); 19 | EXPECT_FALSE(output.hasCenterlineVoxels()); 20 | output.setCenterlineVoxels(new char); 21 | EXPECT_TRUE(output.hasTDF()); 22 | EXPECT_TRUE(output.hasSegmentation()); 23 | EXPECT_TRUE(output.hasCenterlineVoxels()); 24 | } 25 | 26 | TEST(TSFOutputTest, GetHostData) { 27 | TSFOutput output(oul::DeviceCriteria(), new SIPL::int3); 28 | float * TDF = new float[3]; 29 | TDF[0] = 0.5f; 30 | TDF[1] = 1.0f; 31 | TDF[2] = 0.0f; 32 | output.setTDF(TDF); 33 | EXPECT_EQ(0.5f, output.getTDF()[0]); 34 | EXPECT_EQ(1.0f, output.getTDF()[1]); 35 | EXPECT_EQ(0.0f, output.getTDF()[2]); 36 | 37 | char * data = new char[3]; 38 | data[0] = 1; 39 | data[1] = 2; 40 | data[2] = 100; 41 | output.setSegmentation(data); 42 | EXPECT_EQ(1, output.getSegmentation()[0]); 43 | EXPECT_EQ(2, output.getSegmentation()[1]); 44 | EXPECT_EQ(100, output.getSegmentation()[2]); 45 | 46 | char * data2 = new char[3]; 47 | data2[0] = 10; 48 | data2[1] = 20; 49 | data2[2] = 10; 50 | output.setCenterlineVoxels(data2); 51 | EXPECT_EQ(10, output.getCenterlineVoxels()[0]); 52 | EXPECT_EQ(20, output.getCenterlineVoxels()[1]); 53 | EXPECT_EQ(10, output.getCenterlineVoxels()[2]); 54 | } 55 | 56 | TEST(TSFOutputTest, GetSize) { 57 | SIPL::int3 * size = new SIPL::int3(100, 20, 1); 58 | TSFOutput output(oul::DeviceCriteria(), size); 59 | EXPECT_EQ(100, output.getSize()->x); 60 | EXPECT_EQ(20, output.getSize()->y); 61 | EXPECT_EQ(1, output.getSize()->z); 62 | } 63 | 64 | TEST(TSFOutputTest, ShiftVector) { 65 | SIPL::int3 shiftVector(3, 10, 2); 66 | TSFOutput output(oul::DeviceCriteria(), new SIPL::int3); 67 | output.setShiftVector(shiftVector); 68 | EXPECT_EQ(3, output.getShiftVector().x); 69 | EXPECT_EQ(10, output.getShiftVector().y); 70 | EXPECT_EQ(2, output.getShiftVector().z); 71 | } 72 | 73 | -------------------------------------------------------------------------------- /tests/clinicalTests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.hpp" 2 | #include 3 | 4 | class ClinicalTests : public ::testing::Test { 5 | protected: 6 | virtual void SetUp() { 7 | parameters = initParameters(PARAMETERS_DIR); 8 | }; 9 | virtual void TearDown() { 10 | 11 | }; 12 | paramList parameters; 13 | TubeValidation result; 14 | }; 15 | 16 | TubeValidation runClinicalData(paramList parameters, std::string name) { 17 | TSFOutput * output; 18 | output = run(std::string(TESTDATA_DIR) + std::string("/clinical/") + name + std::string("/input.mhd"), parameters, KERNELS_DIR); 19 | 20 | TubeValidation result = validateTube( 21 | output, 22 | std::string(TESTDATA_DIR) + std::string("/clinical/") + name + std::string("/segmentation.mhd"), 23 | std::string(TESTDATA_DIR) + std::string("/clinical/") + name + std::string("/centerline.mhd") 24 | ); 25 | 26 | delete output; 27 | return result; 28 | } 29 | 30 | bool dataExists(std::string name) { 31 | std::string filepath = std::string(TESTDATA_DIR) + std::string("/clinical/") + name + std::string("/input.mhd"); 32 | std::ifstream file(filepath); 33 | bool result = file.good(); 34 | if(!result) 35 | std::cout << "WARNING: Clinical data not found. Aborting clinical test." << std::endl; 36 | file.close(); 37 | return result; 38 | } 39 | 40 | TEST_F(ClinicalTests, LungAirwaysCT) { 41 | std::string name = "Lung-Airways-CT"; 42 | setParameter(parameters, "parameters", name); 43 | 44 | if(!dataExists(name)) { 45 | SUCCEED(); 46 | return; 47 | } 48 | loadParameterPreset(parameters, PARAMETERS_DIR); 49 | try { 50 | result = runClinicalData(parameters, name); 51 | } catch(SIPL::SIPLException e) { 52 | // Out of memory on GPU 53 | // Try to run cropped version instead 54 | std::cout << "Exception occurred. Trying cropped volume instead." << std::endl; 55 | setParameter(parameters, "cropping", "no"); 56 | result = runClinicalData(parameters, name+"-cropped"); 57 | } 58 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 59 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 60 | EXPECT_LT(0.7, result.precision); 61 | EXPECT_LT(0.7, result.recall); 62 | EXPECT_GT(200, result.incorrectCenterpoints); 63 | } 64 | 65 | TEST_F(ClinicalTests, NeuroVesselsMRA) { 66 | std::string name = "Neuro-Vessels-MRA"; 67 | setParameter(parameters, "parameters", name); 68 | 69 | if(!dataExists(name)) { 70 | SUCCEED(); 71 | return; 72 | } 73 | loadParameterPreset(parameters, PARAMETERS_DIR); 74 | try { 75 | result = runClinicalData(parameters, name); 76 | } catch(SIPL::SIPLException e) { 77 | // Out of memory on GPU 78 | // Try to run cropped version instead 79 | std::cout << "Exception occurred. Trying cropped volume instead." << std::endl; 80 | setParameter(parameters, "cropping", "no"); 81 | result = runClinicalData(parameters, name+"-cropped"); 82 | } 83 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 84 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 85 | EXPECT_LT(0.7, result.precision); 86 | EXPECT_LT(0.7, result.recall); 87 | EXPECT_GT(200, result.incorrectCenterpoints); 88 | } 89 | 90 | TEST_F(ClinicalTests, NeuroVesselsUSA) { 91 | std::string name = "Neuro-Vessels-USA"; 92 | setParameter(parameters, "parameters", name); 93 | 94 | if(!dataExists(name)) { 95 | SUCCEED(); 96 | return; 97 | } 98 | loadParameterPreset(parameters, PARAMETERS_DIR); 99 | try { 100 | result = runClinicalData(parameters, name); 101 | } catch(SIPL::SIPLException e) { 102 | // Out of memory on GPU 103 | // Try to run cropped version instead 104 | std::cout << "Exception occurred. Trying cropped volume instead." << std::endl; 105 | setParameter(parameters, "cropping", "no"); 106 | result = runClinicalData(parameters, name+"-cropped"); 107 | } 108 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 109 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 110 | EXPECT_LT(0.7, result.precision); 111 | EXPECT_LT(0.7, result.recall); 112 | EXPECT_GT(200, result.incorrectCenterpoints); 113 | } 114 | 115 | TEST_F(ClinicalTests, PhantomAccUS) { 116 | std::string name = "Phantom-Acc-US"; 117 | setParameter(parameters, "parameters", name); 118 | 119 | if(!dataExists(name)) { 120 | SUCCEED(); 121 | return; 122 | } 123 | loadParameterPreset(parameters, PARAMETERS_DIR); 124 | result = runClinicalData(parameters, name); 125 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 126 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 127 | EXPECT_LT(0.7, result.precision); 128 | EXPECT_LT(0.7, result.recall); 129 | EXPECT_GT(200, result.incorrectCenterpoints); 130 | } 131 | 132 | -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_1/noisy.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_UCHAR 4 | ElementDataFile = noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_1/noisy0.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smistad/Tube-Segmentation-Framework/c6f277dbba17d11bb3da6d6fd51f9ec4638cec14/tests/data/synthetic/dataset_1/noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_1/original.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = original.raw 5 | -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_1/real_centerline.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = real_centerline.raw 5 | -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_2/noisy.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_UCHAR 4 | ElementDataFile = noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_2/noisy0.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smistad/Tube-Segmentation-Framework/c6f277dbba17d11bb3da6d6fd51f9ec4638cec14/tests/data/synthetic/dataset_2/noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_2/original.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = original.raw 5 | -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_2/real_centerline.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = centerline.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_3/noisy.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_UCHAR 4 | ElementDataFile = noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_3/noisy0.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smistad/Tube-Segmentation-Framework/c6f277dbba17d11bb3da6d6fd51f9ec4638cec14/tests/data/synthetic/dataset_3/noisy0.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_3/original.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = original.raw 5 | -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_3/real_centerline.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = centerline.raw -------------------------------------------------------------------------------- /tests/data/synthetic/dataset_4/original.mhd: -------------------------------------------------------------------------------- 1 | NDims = 3 2 | DimSize = 100 100 100 3 | ElementType = MET_CHAR 4 | ElementDataFile = original.raw 5 | -------------------------------------------------------------------------------- /tests/parameterTests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.hpp" 2 | 3 | // Tests for the parameter system 4 | 5 | TEST(ParameterTest, GetDefaultParameters) { 6 | paramList parameters = initParameters(PARAMETERS_DIR); 7 | 8 | EXPECT_FALSE(getParamBool(parameters, "display")); 9 | EXPECT_EQ("gpu", getParamStr(parameters, "device")); 10 | EXPECT_EQ(0.05f, getParam(parameters, "gvf-mu")); 11 | 12 | EXPECT_EQ("Display results", parameters.bools["display"].getDescription()); 13 | EXPECT_EQ("Which type of processor to use", parameters.strings["device"].getDescription()); 14 | EXPECT_EQ("Mu regularization constant of GVF", parameters.numerics["gvf-mu"].getDescription()); 15 | } 16 | 17 | TEST(ParameterTest, SetParameters) { 18 | paramList parameters = initParameters(PARAMETERS_DIR); 19 | 20 | setParameter(parameters, "display", "true"); 21 | EXPECT_TRUE(getParamBool(parameters, "display")); 22 | setParameter(parameters, "cropping", "lung"); 23 | EXPECT_EQ("lung", getParamStr(parameters, "cropping")); 24 | setParameter(parameters, "tdf-high", "0.9"); 25 | EXPECT_EQ(0.9f, getParam(parameters, "tdf-high")); 26 | } 27 | 28 | TEST(ParameterTest, NumericParameterValidation) { 29 | NumericParameter p; 30 | ASSERT_NO_THROW(p = NumericParameter(0.2, 0.1, 1.0, 0.1, "asd", "asd")); 31 | 32 | EXPECT_THROW(p.set(0.05), SIPL::SIPLException); 33 | EXPECT_THROW(p.set(1.2), SIPL::SIPLException); 34 | EXPECT_NO_THROW(p.set(0.4)); 35 | } 36 | 37 | TEST(ParameterTest, StringParameterValidation) { 38 | StringParameter p; 39 | std::vector possibilities; 40 | possibilities.push_back("test"); 41 | possibilities.push_back("moretest"); 42 | possibilities.push_back("evenmoretest"); 43 | 44 | ASSERT_NO_THROW(p = StringParameter("test", possibilities, "asd", "asd")); 45 | 46 | EXPECT_THROW(p.set("abc"), SIPL::SIPLException); 47 | } 48 | 49 | TEST(ParameterTest, Description) { 50 | BoolParameter p(true, "test description", "asd"); 51 | EXPECT_EQ("test description", p.getDescription()); 52 | 53 | NumericParameter p2(0.1, 0.1, 0.2, 0.1, "test description", "asd"); 54 | EXPECT_EQ("test description", p2.getDescription()); 55 | 56 | std::vector possibilities; 57 | StringParameter p3("asd", possibilities, "test description", "asd"); 58 | EXPECT_EQ("test description", p3.getDescription()); 59 | 60 | paramList parameters = initParameters(PARAMETERS_DIR); 61 | EXPECT_EQ("Display results", parameters.bools["display"].getDescription()); 62 | EXPECT_EQ("Extract black or white tubular structures", parameters.strings["mode"].getDescription()); 63 | EXPECT_EQ("Minimum radius of tubular structures", parameters.numerics["radius-min"].getDescription()); 64 | } 65 | 66 | TEST(ParameterTest, Group) { 67 | BoolParameter p(true, "test description", "asd"); 68 | EXPECT_EQ("asd", p.getGroup()); 69 | 70 | NumericParameter p2(0.1, 0.1, 0.2, 0.1, "test description", "asd"); 71 | EXPECT_EQ("asd", p2.getGroup()); 72 | 73 | std::vector possibilities; 74 | StringParameter p3("asd", possibilities, "test description", "asd"); 75 | EXPECT_EQ("asd", p3.getGroup()); 76 | 77 | paramList parameters = initParameters(PARAMETERS_DIR); 78 | EXPECT_EQ("advanced", parameters.bools["display"].getGroup()); 79 | EXPECT_EQ("general", parameters.strings["mode"].getGroup()); 80 | EXPECT_EQ("tube-detection-filter", parameters.numerics["radius-min"].getGroup()); 81 | } 82 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.hpp" 2 | 3 | // Include all the tests here 4 | #include "TSFOutputTests.cpp" 5 | #include "parameterTests.cpp" 6 | #include "tubeSegmentationTests.cpp" 7 | #include "clinicalTests.cpp" 8 | 9 | int main(int argc, char **argv) { 10 | testing::InitGoogleTest(&argc, argv); 11 | return RUN_ALL_TESTS(); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/tests.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TESTS_HPP_ 2 | #define TESTS_HPP_ 3 | 4 | #include "../tube-segmentation.hpp" 5 | #include 6 | #include "../tube-segmentation.cpp" 7 | #include "../parameters.hpp" 8 | #include "../SIPL/Exceptions.hpp" 9 | #include "tubeValidation.cpp" 10 | #include "tsf-config.h" 11 | 12 | #endif /* TESTS_HPP_ */ 13 | -------------------------------------------------------------------------------- /tests/tubeSegmentationTests.cpp: -------------------------------------------------------------------------------- 1 | #include "tests.hpp" 2 | 3 | 4 | TEST(TubeSegmentation, WrongFilenameException) { 5 | paramList parameters = initParameters(PARAMETERS_DIR); 6 | ASSERT_THROW(run("somefilethatdoesntexist.mhd", parameters, KERNELS_DIR), SIPL::IOException); 7 | } 8 | 9 | 10 | class TubeSegmentationPCE : public ::testing::Test { 11 | protected: 12 | virtual void SetUp() { 13 | parameters = initParameters(PARAMETERS_DIR); 14 | setParameter(parameters, "parameters", "Synthetic-Vascusynth"); 15 | setParameter(parameters, "centerline-method", "gpu"); 16 | loadParameterPreset(parameters, PARAMETERS_DIR); 17 | }; 18 | virtual void TearDown() { 19 | 20 | }; 21 | paramList parameters; 22 | TubeValidation result; 23 | }; 24 | 25 | class TubeSegmentationRidge : public ::testing::Test { 26 | protected: 27 | virtual void SetUp() { 28 | parameters = initParameters(PARAMETERS_DIR); 29 | setParameter(parameters, "parameters", "Synthetic-Vascusynth"); 30 | setParameter(parameters, "centerline-method", "ridge"); 31 | loadParameterPreset(parameters, PARAMETERS_DIR); 32 | }; 33 | virtual void TearDown() { 34 | 35 | }; 36 | paramList parameters; 37 | TubeValidation result; 38 | }; 39 | 40 | 41 | TubeValidation runSyntheticData(paramList parameters) { 42 | std::string datasetNr = "1"; 43 | TSFOutput * output; 44 | output = run(std::string(TESTDATA_DIR) + std::string("/synthetic/dataset_") + datasetNr + std::string("/noisy.mhd"), parameters, KERNELS_DIR); 45 | 46 | TubeValidation result = validateTube( 47 | output, 48 | std::string(TESTDATA_DIR) + std::string("/synthetic/dataset_") + datasetNr + std::string("/original.mhd"), 49 | std::string(TESTDATA_DIR) + std::string("/synthetic/dataset_") + datasetNr + std::string("/real_centerline.mhd") 50 | ); 51 | 52 | delete output; 53 | return result; 54 | } 55 | 56 | 57 | TEST_F(TubeSegmentationPCE, SystemTestWithSyntheticDataNormal) { 58 | // Normal execution 59 | setParameter(parameters, "buffers-only", "false"); 60 | setParameter(parameters, "32bit-vectors", "false"); 61 | result = runSyntheticData(parameters); 62 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 63 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 64 | EXPECT_LT(0.7, result.precision); 65 | EXPECT_LT(0.7, result.recall); 66 | } 67 | 68 | TEST_F(TubeSegmentationPCE, SystemTestWithSyntheticData32bit) { 69 | // 32 bit 3D textures 70 | setParameter(parameters, "buffers-only", "false"); 71 | setParameter(parameters, "32bit-vectors", "true"); 72 | result = runSyntheticData(parameters); 73 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 74 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 75 | EXPECT_LT(0.7, result.precision); 76 | EXPECT_LT(0.7, result.recall); 77 | } 78 | 79 | TEST_F(TubeSegmentationPCE, SystemTestWithSyntheticData32bitBuffers) { 80 | // 32 bit buffers 81 | setParameter(parameters, "buffers-only", "true"); 82 | setParameter(parameters, "32bit-vectors", "true"); 83 | result = runSyntheticData(parameters); 84 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 85 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 86 | EXPECT_LT(0.7, result.precision); 87 | EXPECT_LT(0.7, result.recall); 88 | } 89 | 90 | TEST_F(TubeSegmentationPCE, SystemTestWithSyntheticData16bitBuffers) { 91 | // 16 bit buffers 92 | setParameter(parameters, "buffers-only", "true"); 93 | setParameter(parameters, "32bit-vectors", "false"); 94 | result = runSyntheticData(parameters); 95 | EXPECT_GT(1.5, result.averageDistanceFromCenterline); 96 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 97 | EXPECT_LT(0.7, result.precision); 98 | EXPECT_LT(0.7, result.recall); 99 | } 100 | 101 | TEST_F(TubeSegmentationRidge, SystemTestWithSyntheticDataNormal) { 102 | // Normal execution 103 | setParameter(parameters, "buffers-only", "false"); 104 | setParameter(parameters, "32bit-vectors", "false"); 105 | result = runSyntheticData(parameters); 106 | EXPECT_GT(0.5, result.averageDistanceFromCenterline); 107 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 108 | EXPECT_LT(0.7, result.precision); 109 | EXPECT_LT(0.6, result.recall); 110 | } 111 | 112 | TEST_F(TubeSegmentationRidge, SystemTestWithSyntheticData32bit) { 113 | // 32 bit 3D textures 114 | setParameter(parameters, "buffers-only", "false"); 115 | setParameter(parameters, "32bit-vectors", "true"); 116 | result = runSyntheticData(parameters); 117 | EXPECT_GT(0.5, result.averageDistanceFromCenterline); 118 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 119 | EXPECT_LT(0.7, result.precision); 120 | EXPECT_LT(0.6, result.recall); 121 | } 122 | 123 | TEST_F(TubeSegmentationRidge, SystemTestWithSyntheticData32bitBuffers) { 124 | // 32 bit buffers 125 | setParameter(parameters, "buffers-only", "true"); 126 | setParameter(parameters, "32bit-vectors", "true"); 127 | result = runSyntheticData(parameters); 128 | EXPECT_GT(0.5, result.averageDistanceFromCenterline); 129 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 130 | EXPECT_LT(0.7, result.precision); 131 | EXPECT_LT(0.6, result.recall); 132 | } 133 | 134 | TEST_F(TubeSegmentationRidge, SystemTestWithSyntheticData16bitBuffers) { 135 | // 16 bit buffers 136 | setParameter(parameters, "buffers-only", "true"); 137 | setParameter(parameters, "32bit-vectors", "false"); 138 | result = runSyntheticData(parameters); 139 | EXPECT_GT(0.5, result.averageDistanceFromCenterline); 140 | EXPECT_LT(75.0, result.percentageExtractedCenterlines); 141 | EXPECT_LT(0.7, result.precision); 142 | EXPECT_LT(0.6, result.recall); 143 | } 144 | 145 | -------------------------------------------------------------------------------- /tests/tubeValidation.cpp: -------------------------------------------------------------------------------- 1 | #include "../SIPL/Core.hpp" 2 | #include "../tube-segmentation.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace SIPL; 8 | 9 | typedef struct TubeValidation { 10 | float averageDistanceFromCenterline; 11 | float percentageExtractedCenterlines; 12 | float recall; 13 | float precision; 14 | unsigned int incorrectCenterpoints; 15 | } TubeValidation; 16 | 17 | TubeValidation getValidationMeasures( 18 | Volume * realCenterlines, 19 | Volume * eCenterlines, 20 | Volume * original, 21 | Volume * segmentation, 22 | bool visualize 23 | ) { 24 | TubeValidation result; 25 | Volume * detectedCenterlines = new Volume(realCenterlines->getSize()); 26 | detectedCenterlines->fill(0); 27 | 28 | float avgDistance = 0.0f; 29 | int counter = 0; 30 | unsigned int incorrectCenterpoints = 0; 31 | int width = eCenterlines->getWidth(); 32 | int height = eCenterlines->getHeight(); 33 | int depth = eCenterlines->getDepth(); 34 | int maxRadius = 4; 35 | for(int z = 0; z < depth; z++) { 36 | for(int y = 0; y < height; y++) { 37 | for(int x = 0; x < width; x++) { 38 | if(eCenterlines->get(x,y,z) == 1) { 39 | int3 start(x,y,z); 40 | bool found = false; 41 | float bestDistance = 9999.0f; 42 | 43 | for(int a = -maxRadius; a <= maxRadius; a++) { 44 | for(int b = -maxRadius; b <= maxRadius; b++) { 45 | for(int c = -maxRadius; c <= maxRadius; c++) { 46 | int3 n = start + int3(a,b,c); 47 | if(eCenterlines->inBounds(n) && 48 | start.distance(n) < maxRadius) { 49 | if(realCenterlines->get(n) == 1) { 50 | found = true; 51 | if(start.distance(n) < bestDistance) { 52 | bestDistance = start.distance(n); 53 | } 54 | detectedCenterlines->set(n, 1); 55 | } 56 | } 57 | }}} 58 | if(!found) { 59 | incorrectCenterpoints++; 60 | } else { 61 | avgDistance += bestDistance; 62 | counter++; 63 | } 64 | } 65 | }}} 66 | avgDistance /= counter; 67 | result.averageDistanceFromCenterline = avgDistance; 68 | result.incorrectCenterpoints = incorrectCenterpoints; 69 | 70 | 71 | std::cout << std::endl; 72 | std::cout << "Centerline result" << std::endl << "--------------------" << std::endl; 73 | std::cout << "Average distance from real centerline was: " << avgDistance << " voxels" << std::endl; 74 | std::cout << "Number of incorrect centerpoints: " << incorrectCenterpoints << std::endl; 75 | 76 | int detected = 0; 77 | int undetected = 0; 78 | Volume * visualization = new Volume(detectedCenterlines->getSize()); 79 | for(int i = 0; i < visualization->getTotalSize(); i++) { 80 | float3 v; 81 | if(detectedCenterlines->get(i) == 1) { // flag detected point 82 | v.x = 1.0f; 83 | detected++; 84 | } 85 | if(realCenterlines->get(i) == 1 && detectedCenterlines->get(i) == 0) { // undetected centerpoint 86 | v.z = 1.0f; 87 | undetected++; 88 | } 89 | //if(eCenterlines->get(i) == 1) // flag the actual extracted points 90 | // v.y = 1.0f; 91 | visualization->set(i, v); 92 | } 93 | if(visualize) 94 | visualization->showMIP(); 95 | 96 | result.percentageExtractedCenterlines = (float)detected*100.0f/(detected+undetected); 97 | std::cout << "Percentage of extracted centerline: " << (float)detected*100.0f/(detected+undetected) << std::endl << std::endl; 98 | 99 | int extracted = 0; 100 | for(int i = 0; i < eCenterlines->getTotalSize(); i++) { 101 | if(eCenterlines->get(i) == 1) 102 | extracted++; 103 | } 104 | std::cout << "Total nr of real centerpoints: " << detected+undetected << std::endl; 105 | std::cout << "Total nr of extracted centerpoints: " << extracted << std::endl; 106 | std::cout << "Ratio: " << (float)extracted/(detected+undetected) << std::endl << std::endl; 107 | 108 | 109 | 110 | // for segmentation measure: true positives, false positives, false negatives, false positives 111 | int truePositives = 0; 112 | int falsePositives = 0; 113 | int trueNegatives = 0; 114 | int falseNegatives = 0; 115 | for(int i = 0; i < original->getTotalSize(); i++) { 116 | bool truth = original->get(i) == 1; 117 | bool test = segmentation->get(i) == 1; 118 | if(truth && test) { 119 | truePositives++; 120 | } else if(!truth && test) { 121 | falsePositives++; 122 | } else if(!truth && !test) { 123 | trueNegatives++; 124 | } else { 125 | falseNegatives++; 126 | } 127 | } 128 | std::cout << "Segmentation result" << std::endl << "--------------------" << std::endl; 129 | std::cout << "True positives: " << truePositives << std::endl; 130 | std::cout << "False positives: " << falsePositives << std::endl; 131 | std::cout << "True negatives: " << trueNegatives << std::endl; 132 | std::cout << "False negatives: " << falseNegatives << std::endl << std::endl; 133 | 134 | result.recall = (float)truePositives / (truePositives+falseNegatives); 135 | result.precision = (float)truePositives / (truePositives+falsePositives); 136 | std::cout << "Recall: " << result.recall << std::endl; 137 | std::cout << "Precision: " << result.precision << std::endl; 138 | return result; 139 | } 140 | 141 | 142 | TubeValidation validateTube(TSFOutput * output, std::string segmentationPath, std::string centerlinePath) { 143 | 144 | Volume * realCenterlines = new Volume(centerlinePath.c_str()); 145 | 146 | Volume * original = new Volume(segmentationPath.c_str()); 147 | 148 | 149 | // load extracted centerlines 150 | Volume * extractedCenterlines = new Volume(*(output->getSize())); 151 | extractedCenterlines->setData(output->getCenterlineVoxels()); 152 | 153 | Volume * segmentation = new Volume(*(output->getSize())); 154 | segmentation->setData(output->getSegmentation()); 155 | 156 | TubeValidation result = getValidationMeasures(realCenterlines, extractedCenterlines, original, segmentation, false); 157 | 158 | return result; 159 | 160 | } 161 | -------------------------------------------------------------------------------- /timing.hpp: -------------------------------------------------------------------------------- 1 | #ifdef CPP11 2 | #include 3 | using std::chrono::high_resolution_clock; 4 | using std::chrono::duration_cast; 5 | using std::chrono::milliseconds; 6 | #define INIT_TIMER high_resolution_clock::time_point timerStart = high_resolution_clock::now(); 7 | #define START_TIMER timerStart = high_resolution_clock::now(); 8 | #define STOP_TIMER(name) std::cout << "RUNTIME of " << name << ": " << \ 9 | duration_cast( \ 10 | high_resolution_clock::now()-timerStart \ 11 | ).count() << " ms " << std::endl; 12 | #else 13 | #define INIT_TIMER 14 | #define START_TIMER 15 | #define STOP_TIMER(name) 16 | #endif 17 | -------------------------------------------------------------------------------- /tube-segmentation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TUBE_SEGMENTATION 2 | #define TUBE_SEGMENTATION 3 | 4 | #define CL_USE_DEPRECATED_OPENCL_1_1_APIS 5 | 6 | #include "OpenCLUtilityLibrary/OpenCLManager.hpp" 7 | #include "SIPL/Types.hpp" 8 | #include 9 | #include 10 | #include 11 | #ifdef CPP11 12 | #include 13 | using std::unordered_map; 14 | #else 15 | #include 16 | using boost::unordered_map; 17 | #endif 18 | #include "parameters.hpp" 19 | #include "SIPL/Exceptions.hpp" 20 | #include "inputOutput.hpp" 21 | 22 | typedef struct TubeSegmentation { 23 | float *Fx, *Fy, *Fz; // The GVF vector field 24 | float *FxSmall, *FySmall, *FzSmall; // The GVF vector field 25 | float *TDF; // The TDF response 26 | float *radius; 27 | char *centerline; 28 | char *segmentation; 29 | float *intensity; 30 | } TubeSegmentation; 31 | 32 | 33 | /* 34 | * For debugging. 35 | */ 36 | void print(paramList parameters); 37 | 38 | cl::Image3D readDatasetAndTransfer(OpenCL &ocl, std::string, paramList ¶meters, SIPL::int3 *, TSFOutput *); 39 | 40 | void runCircleFittingAndRidgeTraversal(OpenCL *, cl::Image3D *dataset, SIPL::int3 * size, paramList ¶meters, TSFOutput *); 41 | 42 | void runCircleFittingAndNewCenterlineAlg(OpenCL *, cl::Image3D *dataset, SIPL::int3 * size, paramList ¶meters, TSFOutput *); 43 | 44 | void runCircleFittingAndTest(OpenCL *, cl::Image3D *dataset, SIPL::int3 * size, paramList ¶meters, TSFOutput *); 45 | 46 | 47 | TSFOutput * run(std::string filename, paramList ¶meters, std::string kernel_dir); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /tubeDetectionFilters.cpp: -------------------------------------------------------------------------------- 1 | #include "tubeDetectionFilters.hpp" 2 | using namespace cl; 3 | 4 | void runSplineTDF( 5 | OpenCL &ocl, 6 | SIPL::int3 &size, 7 | Image3D *vectorField, 8 | Buffer *TDF, 9 | Buffer *radius, 10 | float radiusMin, 11 | float radiusMax, 12 | float radiusStep 13 | ) { 14 | 15 | /* 16 | // Create blending functions 17 | int samples = 3; 18 | float s = 0.5; 19 | float * blendingFunctions = new float[4*samples]; // 4 * samples per arm 20 | for(int i = 0; i < samples; i++) { 21 | float u = (float)i / (samples-1); 22 | blendingFunctions[i*4] = -s*u*u*u + 2*s*u*u - s*u; 23 | blendingFunctions[i*4+1] = (2-s)*u*u*u + (s-3)*u*u + 1; 24 | blendingFunctions[i*4+2] = (s-2)*u*u*u + (3-2*s)*u*u + s*u; 25 | blendingFunctions[i*4+3] = s*u*u*u - s*u*u; 26 | } 27 | 28 | // Transfer to device 29 | Buffer bufferBlendingFunctions = Buffer( 30 | ocl.context, 31 | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, 32 | sizeof(float)*samples*4, 33 | blendingFunctions 34 | ); 35 | */ 36 | 37 | Kernel TDFKernel(ocl.program, "splineTDF"); 38 | TDFKernel.setArg(0, *vectorField); 39 | TDFKernel.setArg(1, *TDF); 40 | TDFKernel.setArg(2, std::max(1.0f, radiusMin)); 41 | TDFKernel.setArg(3, radiusMax); 42 | TDFKernel.setArg(4, radiusStep); 43 | //TDFKernel.setArg(5, bufferBlendingFunctions); 44 | TDFKernel.setArg(5, 12); // arms 45 | //TDFKernel.setArg(6, samples); // samples per arm 46 | TDFKernel.setArg(6, *radius); 47 | TDFKernel.setArg(7, 0.1f); 48 | 49 | ocl.queue.enqueueNDRangeKernel( 50 | TDFKernel, 51 | NullRange, 52 | NDRange(size.x,size.y,size.z), 53 | NDRange(4,4,4) 54 | ); 55 | } 56 | 57 | void runCircleFittingTDF(OpenCL &ocl, SIPL::int3 &size, Image3D * vectorField, Buffer * TDF, Buffer * radius, float radiusMin, float radiusMax, float radiusStep) { 58 | Kernel circleFittingTDFKernel(ocl.program, "circleFittingTDF"); 59 | circleFittingTDFKernel.setArg(0, *vectorField); 60 | circleFittingTDFKernel.setArg(1, *TDF); 61 | circleFittingTDFKernel.setArg(2, *radius); 62 | circleFittingTDFKernel.setArg(3, radiusMin); 63 | circleFittingTDFKernel.setArg(4, radiusMax); 64 | circleFittingTDFKernel.setArg(5, radiusStep); 65 | 66 | ocl.queue.enqueueNDRangeKernel( 67 | circleFittingTDFKernel, 68 | NullRange, 69 | NDRange(size.x,size.y,size.z), 70 | NDRange(4,4,4) 71 | ); 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /tubeDetectionFilters.hpp: -------------------------------------------------------------------------------- 1 | #include "commons.hpp" 2 | #include "SIPL/Types.hpp" 3 | using namespace cl; 4 | 5 | void runSplineTDF( 6 | OpenCL &ocl, 7 | SIPL::int3 &size, 8 | Image3D *vectorField, 9 | Buffer *TDF, 10 | Buffer *radius, 11 | float radiusMin, 12 | float radiusMax, 13 | float radiusStep 14 | ); 15 | void runCircleFittingTDF(OpenCL &ocl, SIPL::int3 &size, Image3D * vectorField, Buffer * TDF, Buffer * radius, float radiusMin, float radiusMax, float radiusStep); 16 | --------------------------------------------------------------------------------