Super-build
super-build是一种组织大型项目的方式,本身也是基于cmake,一般项目下会有SuperBuild.cmake文件。一般大型项目都会依赖很多相对较小的库(MITK <--- ITK、VTK、blabla),一般情况下需要手动下载每个依赖库,编译生成,最后再设置要调用的库。依赖库少点还好说,多了直接就哭了。比如MITK这种大型项目,动辄几十个依赖,挨个编完黄花菜都凉了。Super-build其实就相当不止写了自己的cmake,把其他依赖项目的cmake也都包含到自己的build-chain里,设置好依赖关系以后一键编译。
MITK编译的坑
MITK本身没啥问题,问题在它依赖的SimpleITK(0.8)和Python(2.6.7?)有点古老。并且默认Python不编译SSL模块,基本跟pip无缘了,也没法在MITK的Python Module里直接操作数据了。普通编译使用没问题,要想切某个库的版本,必须改变Super-build的rule,这也算super-build的弊端之一吧。
切换Python版本
super build会优先下载
python-cmake-buildsystem
于
build/ep/src/Python-stamp/extract-Python.cmake
中进行,该处会对python也进行superbuild,生成build/ep/src/Python
,再下载对应版本python八卦一句,Python好像从来都没支持过CMake,这个buildsystem是第三方的
/home/liuxinglong01/1HDD/work/MITK-2016.11/build/ep/src/Python/CMakeLists.txt 下,
set(PY_VERSION_MAJOR 2) set(PY_VERSION_MINOR 7) set(PY_VERSION_PATCH 13)
/home/liuxinglong01/1HDD/work/MITK-2016.11/CMakeExternals/Python.cmake 下,
set(MITK_PYTHON_MAJOR_VERSION 2) set(MITK_PYTHON_MINOR_VERSION 7) set(MITK_PYTHON_PATCH_VERSION 13)
并修改下载包
ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} URL https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz
编译支持SSL版本的Python 对ubuntu来说安装 libssl-dev cmake下Python-build目录,注意看提示,是否找到SSL的位置,如果没有手动添加
- OPENSSL_INCLUDE_DIR : /usr/local/openssl/include and - OPENSSL_LIBRARIES : /usr/local/openssl/lib/libcrypto.a;/usr/local/openssl/lib/libssl.a or - OPENSSL_CRYPTO_LIBRARY: /usr/local/openssl/lib/libcrypto.so - OPENSSL_SSL_LIBRARY: /usr/local/openssl/lib/libssl.so
然后更改文件 Modules/Setup.dist,把206到209这3行反注释(默认是被注释掉的),然后保存
SSL=/usr/local/ssl ssl _ssl.c -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl -L$(SSL)/lib -lssl -
测试是否支持SSL
>> import socket >> hasattr()
Python UCS-4 支持,万恶的Unicode 判断是否UCS-4
>>> import sys >>> print sys.maxunicode >>> 65535 = UCS2, 1114111 = UCS4
如果直接使用configure,
>>> ./configure --enable-unicode=ucs4
如果使用较低版本的python-cmake-build-system,参照 https://github.com/python-cmake-buildsystem/python-cmake-buildsystem/commit/58d678169bbdbb26fd1cca2213ddd8e9e62c7dcd 的解决方式,直接 cmake/ConfigureChecks.cmake
>>> set(PY_UNICODE_TYPE "unsigned short") >>> set(HAVE_USABLE_WCHAR_T 0) >>> set(Py_UNICODE_SIZE 4)
SimpleITK and SimpleElastix
SimpleITK集合了ITK的功能,并且使用Swig提供了Python绑定,将ITK的功能都导出到了Python中,SITK也基于SuperBuild提供了一种完全自动化的C++和PythonBinding的方式。
原始的ITK从大概2014年(?)开始支持一些GPU Filter,但是非常有限,现在能支持的也没几个;elastix是一个基于ITK的配准的库,但是内部集成了非常多的滤波器的OpenCL实现;SimpleElastix是最近发起的一个项目,旨在将elastix集成进SimpleITK的框架中。
胸部CT天生是三维数据,各种滤波(中值、高斯、双边、blabla),以及非常重要的三维重采样方法都比较适合GPU实现,同时考虑到DeepLearning的代码都在Python实现,因此采用了结合SimpleITK的SimpleElastix,将C++的OpenCL代码通过Swig映射到Python端,当前主要考虑三维重采,但通过扩展方式部署更多GPU实现应该也不难。
SimpleElastix项目还非常年轻,貌似维护的也没几个人,当前代码的主要问题是和ITK理不断剪还乱的关系;ITK内部有一些GPU支持的遗留,这些在Elastix都被保留和发展了,自然造成一些bug;
在Elastix上提的issue一直还在开着...
https://github.com/SuperElastix/elastix/issues/37
I have extended the ElastixImageFilter with OpenCLResampler included in the original Elastix repo. When using itkGPUImage, the GPU memory will not be released since the reference counter is always set to at least 2. One ref is from the GPUImage itself, but the other ref is coming from the internal GPUImageDataManager inside the class (with SetImagePointer call ), which leads to a recurrent smart pointer counter. And the itkGPUImage allocated will never be released until the whole programme exits. I have simply altered the SetImagePointer signature to allow this method accept normal pointers but not smart pointer to avoid reference increment. Not sure if this is the best solution, but it surely is the simplest. I believe this should be some bug to the original Elastix package, however I do not find any issue reporting for this package on Github, therefore I place it here to someone who may encounter the same problem. P.S. The usual CPU image (itk::Image) works well, this can be observed by the Print function after image allocation. Typically, when you allocate a new itk::Image the default reference count is 1, and the image would be releases out of function scope. Meanwhile, when you allocate a new itk::GPUImage, the ref count is exactly the same 1, but after graft data using methods like GraftITK from CPU itk Images, the ref count is set as 2, in which the 2nd ref is set by GPUImageDataManager, making the GPU image not properly released.
修正方法很暴力:
According to the superbuild External_ITK.cmake, the
ITK_TAG_COMMAND GIT_TAG v4.12.2
. It seems that the repo automatically is using ITK 4.12.2.Meanwhile, there are two files called itkGPUImageDataManager.h/hxx where one in ITK standard directory and one in Elastix\Common\OpenCL\ITKImprovements. I have changed the latter one looking like:
// void SetImagePointer( typename ImageType::Pointer img ); void SetImagePointer( ImageType* img );
This is just expedient but works for me.
As what I have mentioned before, the smart pointer causes a cycle reference
typename GPUImageDataManager< GPUImage >::Pointer m_DataManager;
in itkGPUImage.h declares the m_DataManager is a member of itkGPUImage, meanwhile the
template< typename TPixel, unsigned int VImageDimension > void GPUImage< TPixel, VImageDimension > ::AllocateGPU( void ) { if( !m_Graft ) { // allocate GPU memory this->ComputeOffsetTable(); const unsigned long numPixel = this->GetOffsetTable()[ VImageDimension ]; m_DataManager->SetBufferSize( sizeof( TPixel ) * numPixel ); m_DataManager->SetImagePointer( this ); // adding reference here m_DataManager->SetCPUBufferPointer( Superclass::GetBufferPointer() ); m_DataManager->Allocate(); /* prevent unnecessary copy from CPU to GPU at the beginning */ m_DataManager->SetTimeStamp( this->GetTimeStamp() ); } }
is referred by m_DataManager, making the GPUImage memory not released.
Although I have been using ITK for years, I am still new to elastix as well as OpenCL, feel free to tell me if I have wrong understanding of the code. Thanks
NOTE:新版的ITK已经解决了这个bug,在itkGPUImageDataManager中,m_Image不再是一个普通的智能指针,而是弱指针:
WeakPointer<ImageType> m_Image;
且
... SetImagePointer(... img) { .... m_Image = img.GetPointer(); .... }
作者 Xinglong Liu | 发布于 2017-01-18