7.1 與ITK的關(guān)系
elastix代碼的很大一部分是基于ITK Ib′a?nez et al[2005]回俐。 ITK的使用意味著低級功能(圖像類,內(nèi)存分配等)經(jīng)過徹底測試惰蜜。 當(dāng)然,由ITK支持的所有圖像格式elastix也支持受神。 可以使用各種編譯器(MS Visual Studio抛猖,GCC)在多個操作系統(tǒng)(Windows XP,Linux鼻听,Mac OS X)上編譯C ++源代碼财著,并支持32位和64位系統(tǒng)。
除了現(xiàn)有的ITK圖像配準(zhǔn)類之外撑碴,elastix還實(shí)現(xiàn)了新的功能撑教。 最重要的增強(qiáng)功能列在表7.1中。 請注意醉拓,從第4版起伟姐,ITK還具有變換級聯(lián),并支持空間導(dǎo)數(shù)(但不是其導(dǎo)數(shù)再次為μ)亿卤。
7.2 elastix代碼概述
elastix源代碼大致分為兩層愤兵,分別以C ++編寫:A)實(shí)現(xiàn)圖像配準(zhǔn)功能的ITK風(fēng)格的類,以及B)elastix包裝器怠噪,它兼容閱讀和設(shè)置參數(shù)恐似,實(shí)例化和連接組件,保存(中間) 結(jié)果和類似的“行政”任務(wù)傍念。 模塊化設(shè)計可以添加新的組件矫夷,而無需更改elastix core。 添加一個新組件首先創(chuàng)建一個層A類憋槐,可以獨(dú)立于B層進(jìn)行編譯和測試双藕。接下來,需要寫一個小B層包裝阳仔,將A類與elastix的其他部分連接起來忧陪。
例如,圖像采樣器被實(shí)現(xiàn)為所有從基類itk :: ImageSamplerBase繼承的ITK類近范。 這些可以在src / Common / ImageSamplers中找到嘶摊。 這是elastix的“層A”。 對于每個采樣器(隨機(jī)评矩,網(wǎng)格叶堆,完整...),寫入一個包裝器斥杜,位于src / Components / ImageSamplers中虱颗,它在配準(zhǔn)過程的每個新分辨率之前都需要配置采樣器沥匈。 這是elastix的“B層”。
7.2.1 目錄結(jié)構(gòu)
基本目錄結(jié)構(gòu)如下:
- dox
- src/Common: ITK類忘渔,Layer A stuff高帖。 該目錄還包含一些與ITK無關(guān)的外部庫,如xout(由我們編寫)和ANNlib畦粮。
- src/Core: 這是主要的elasticix內(nèi)核散址,負(fù)責(zé)執(zhí)行流程,連接類宣赔,讀取參數(shù)等爪飘。
- 抽樣策略的模塊化框架。See for more details Staring and
Klein [2010b]. - 幾個新的優(yōu)化者:Kiefer-Wolfowitz拉背,Robbins-Monro,自適應(yīng)隨機(jī)梯度下降默终,進(jìn)化策略椅棺。 對現(xiàn)有ITK優(yōu)化器進(jìn)行完整的返工,增加用戶控制和更好的錯誤處理:準(zhǔn)牛頓齐蔽,非線性共軛梯度两疚。
- 幾個新的或更靈活的成本函數(shù):(標(biāo)準(zhǔn)化)互信息,用Parzen窗口實(shí)現(xiàn)含滴,類似于Th'evenaz and Unser [2000]诱渤,多重α相互信息,彎曲能量懲罰項(xiàng)谈况,剛性懲罰項(xiàng)勺美。
- 連接任意數(shù)量幾何變換的能力。
- 轉(zhuǎn)換不僅支持?T/?μ的計算碑韵,而且還支持空間導(dǎo)數(shù)?T/?x和?2T/?x2的計算赡茸,并且它們的導(dǎo)數(shù)為μ的計算,用于計算正則化項(xiàng)時經(jīng)常需要祝闻。 另外占卧,更一般地集成了某些轉(zhuǎn)換的緊湊支持。 更多詳情查看Staring and Klein [2010a]联喘。
- 成本函數(shù)的線性組合华蜒,而不是單一的成本函數(shù)。
- 抽樣策略的模塊化框架。See for more details Staring and
表7.1:與ITK相比豁遭,elastix的最重要的增強(qiáng)和增加
- src/Components: 此目錄包含組件及其elastix包裝器(B層)叭喜。 非常特定的組件A層代碼也可以在這里找到。
在elastix 4.4和更高版本中堤框,還可以添加您自己的組件目錄域滥。 這些可以位于elastix源樹之外的任何地方纵柿。 有關(guān)詳細(xì)信息,請參見第7.4節(jié)启绰。
7.3 在自己的軟件中使用elastix
在自己的軟件中有(至少)三種使用彈性的方式:
- 編譯elastix可執(zhí)行文件昂儒,并直接用適當(dāng)?shù)膮?shù)調(diào)用它。 這是最簡單的方法委可,Matlab和MeVisLab代碼存在渊跋。
- 將elastix源代碼包含在您自己的項(xiàng)目中,請參見第7.3.1節(jié)着倾。
- 將elastix編譯為庫并鏈接到該庫拾酝,請參見第7.3.2節(jié)。 此功能從版本4.7(2014年2月)起可用卡者。
7.3.1 在您自己的軟件中包括elasticix代碼
您可能會發(fā)現(xiàn)一些elastix類有助于集成到您自己的項(xiàng)目中蒿囤。 例如,如果您正在開發(fā)一個新的elasticix組件崇决,并且首先要在elasticix之外測試(參見第7.4節(jié))材诽,在這種情況下,您當(dāng)然可以將所需的elastix文件復(fù)制到您自己的項(xiàng)目中恒傻,或者手動設(shè)置包含路徑脸侥,但這不會很方便。
為了更容易盈厘,在elastix二進(jìn)制目錄中生成一個UseElastix.cmake文件睁枕。 您可以將其包含在您自己的項(xiàng)目的CMakeLists.txt文件中,并且CMake將確保設(shè)置了所有必需的包含目錄沸手。 此外外遇,您可以鏈接到elastix庫,如elxCommon罐氨,以避免重新編譯代碼臀规。
一個例子可以在elastix source distribution的目錄dox / externalproject中找到。
7.3.2 使用elastix作為庫
簡介
elastix還提供了用作動態(tài)或靜態(tài)鏈接庫的可能性栅隐。 這提供了將其功能集成到您自己的軟件中的可能性塔嬉,而無需調(diào)用外部的elasticix可執(zhí)行文件。 后者的缺點(diǎn)是租悄,您的軟件(可能已經(jīng)在內(nèi)存中已經(jīng)有固定和運(yùn)動的圖像)必須將這些圖像存儲到磁盤谨究。 然后,elastix將再次加載它們(因此它們將在內(nèi)存中兩次)泣棋,執(zhí)行配準(zhǔn)胶哲,并將結(jié)果寫入磁盤。 然后潭辈,您的軟件需要將結(jié)果從磁盤加載到內(nèi)存鸯屿。 這種方法顯然導(dǎo)致內(nèi)存使用的增加澈吨,由于讀/寫開銷而導(dǎo)致的性能下降,并不是非常優(yōu)雅寄摆。 當(dāng)使用elastix作為庫時谅辣,您的軟件只需要將存儲器指針傳遞給庫接口,因此不需要讀/寫或內(nèi)存映像復(fù)制婶恼。 配準(zhǔn)之后桑阶,elastix將會將指針返回到您的程序。
庫功能還在相當(dāng)?shù)纳钊腴_發(fā)中勾邦,但是以下基本功能已經(jīng)可用:
- 使用elastix組件的任何組合配準(zhǔn)任何一對圖像蚣录。
- 配準(zhǔn)掩碼(masks)的使用
- 連續(xù)使用多個參數(shù)文件(類似于使用elastix可執(zhí)行文件的-p選項(xiàng)多次)。
- 使用transformix來轉(zhuǎn)換圖像眷篇。
將elastix作為靜態(tài)或動態(tài)庫構(gòu)建
要構(gòu)建elastix庫作為庫萎河,您必須禁用CMake中的ELASTIX_BUILD_ EXECUTABLE選項(xiàng)。 使用此選項(xiàng)禁用一個構(gòu)建項(xiàng)目蕉饼,將創(chuàng)建一個靜態(tài)庫公壤。 如果要創(chuàng)建動態(tài)庫(測試不夠好),則必須啟用ELASTIX_BUILD_SHARED_ LIBS選項(xiàng)椎椰。
與elastix庫連接
在構(gòu)建自己的軟件項(xiàng)目時,需要將elastix連接沾鳄,并將elastix源目錄作為包含目錄提供給編譯器慨飘。 您可以這樣做,例如译荞,通過將以下代碼添加到您的CMakeLists.txt文件中:
set( ELASTIX_BUILD_DIR "" CACHE PATH "Path to elastix build folder" )
set( ELASTIX_USE_FILE ${ELASTIX_BUILD_DIR}/UseElastix.cmake )
if( EXISTS ${ELASTIX_USE_FILE} )
include( ${ELASTIX_USE_FILE} )
link_libraries( param )
link_libraries( elastix )
link_libraries( transformix )
endif()
這將為CMake添加一個參數(shù)瓤的,ELASTIX_BUILD_DIR,需要在運(yùn)行CMake時由用戶提供吞歼。 您應(yīng)該提供您編譯elastix源代碼的目錄(具有UseElastix.cmake文件的目錄)圈膏。 如果要更好地控制鏈接elastix的二進(jìn)制文件,請使用CMaketarget_link_libraries指令篙骡。
準(zhǔn)備配準(zhǔn)參數(shù)設(shè)置
要能夠運(yùn)行elastix稽坤,您需要首先準(zhǔn)備參數(shù)設(shè)置。 例如你可以通過從文件中讀取它們來做到這一點(diǎn):
#include "elastixlib.h"
#include "itkParameterFileParser.h"
using namespace elastix;
typedef ELASTIX::ParameterMapType RegistrationParametersType;
typedef itk::ParameterFileParser ParserType;
// Create parser for transform parameters text file.
ParserType::Pointer file_parser = ParserType::New();
// Try parsing transform parameters text file.
file_parser->SetParameterFileName( "par_registration.txt" );
try
{
file_parser->ReadParameterFile();
}
catch( itk::ExceptionObject & e )
{
std::cout << e.what() << std::endl;
// Do some error handling!
}
// Retrieve parameter settings as map.
RegistrationParametersType parameters = file_parser->GetParameterMap();
如果要連續(xù)使用多個參數(shù)文件糯俗,請逐個加載它們尿褪,并將它們添加到向量中:
typedef std::vector<RegistrationParametersType> RegistrationParametersContainerType;
然后在下面的代碼中使用此向量,而不是單個參數(shù)映射得湘。 您還可以在C ++代碼中設(shè)置參數(shù)映射杖玲。 檢查ELASTIX :: ParameterMapType的typedef的確切格式。
運(yùn)行elastix
加載參數(shù)設(shè)置后淘正,使用例如以下代碼運(yùn)行elastix:
ELASTIX* elastix = new ELASTIX();
int error = 0;
try
{
error = elastix->RegisterImages(
static_cast<typename itk::DataObject::Pointer>( fixed_image.GetPointer() ),
static_cast<typename itk::DataObject::Pointer>( moving_image.GetPointer() ),
parameters, // Parameter map read in previous code
output_directory, // Directory where output is written, if enabled
write_log_file, // Enable/disable writing of elastix.log
output_to_console, // Enable/disable output to console
0, // Provide fixed image mask (optional, 0 = no mask)
0 // Provide moving image mask (optional, 0 = no mask)
);
}
catch( itk::ExceptionObject &err )
{
// Do some error handling.
}
if( error == 0 )
{
if( elastix->GetResultImage().IsNotNull() )
{
// Typedef the ITKImageType first...
ITKImageType * output_image = static_cast<ITKImageType *>(
elastix->GetResultImage().GetPointer() );
}
else
{
// Registration failure. Do some error handling.
}
// Get transform parameters of all registration steps.
RegistrationParametersContainerType transform_parameters = elastix->GetTransformParameterMapList();
// Clean up memory.
delete elastix;
運(yùn)行transformix
由ELASTIX類提供的轉(zhuǎn)換參數(shù)摆马,您可以運(yùn)行transformix:
TRANSFORMIX* transformix = new TRANSFORMIX();
int error = 0;
try
{
error = transformix->TransformImage(
static_cast<typename itk::DataObject::Pointer>(
input_image_adapter.GetPointer() ),
transform_parameters, // Parameters resulting from elastix run
write_log_file, // Enable/disable writing of transformix.log
output_to_console); // Enable/disable output to console
}
catch( itk::ExceptionObject &err )
{
// Do some error handling.
}
if( error == 0 )
{
// Typedef the ITKImageType first...
ITKImageType * output_image = static_cast<ITKImageType *>(
transformix->GetResultImage().GetPointer() );
}
else
{
// Do some error handling.
}
// Clean up memory.
delete transformix;
或者臼闻,您可以使用參數(shù)文件解析器從文件(例如,從TransformParameters.0.txt)讀取轉(zhuǎn)換參數(shù)囤采,方法與上述配準(zhǔn)參數(shù)所示相同述呐。
7.4 創(chuàng)建新組件
如果要創(chuàng)建自己的組件,開始編寫A層類是很自然的斑唬,而不用擔(dān)心elastix市埋。 A層過濾器應(yīng)該實(shí)現(xiàn)所有基本功能,并且可以在單獨(dú)的ITK程序中進(jìn)行測試恕刘,如果它執(zhí)行了應(yīng)該做的事情缤谎。 一旦你獲得了這個ITK類的工作,在elastix文件(從現(xiàn)有組件復(fù)制粘貼開始)中編寫B(tài)層包裝很簡單褐着。
使用CMake坷澡,您可以通過使用“ELASTIX_USER_COMPONENT_DIRS”選項(xiàng)告訴elastix你的新組件的源代碼在哪些目錄中,來了解新組件的源代碼所在目錄的彈性含蓉。 elastix將搜索包含ADD ELXCOMPONENT(<name> ...)命令的CMakeLists.txt文件的這些目錄的所有子目錄频敛。 伴隨elastix組件的CMakeLists.txt文件通常如下所示:
ADD_ELXCOMPONENT( AdvancedMeanSquaresMetric
elxAdvancedMeanSquaresMetric.h
elxAdvancedMeanSquaresMetric.hxx
elxAdvancedMeanSquaresMetric.cxx
itkAdvancedMeanSquaresImageToImageMetric.h
itkAdvancedMeanSquaresImageToImageMetric.hxx )
ADD_ELXCOMPONENT命令是在src / Components / CMakeLists.txt中定義的宏。 第一個參數(shù)是B層包裝類的名稱馅扣,它在“elxAdvancedMeanSquaresMetric.h”中聲明斟赚。 之后,您可以指定組件所依賴的源文件差油。 在上面的例子中拗军,以“itk”開頭的文件形成了A層代碼。 以“elx”開頭的文件是B層代碼蓄喇。 文件“elxAdvancedMeanSquaresMetric.cxx”特別簡單发侵。 它只包括兩行:
#include "elxAdvancedMeanSquaresMetric.h"
elxInstallMacro( AdvancedMeanSquaresMetric );
elxInstallMacro在src / Core / Install / elxMacro.h中定義。
文件elxAdvancedMeanSquaresMetric.h / hxx一起定義了B層包裝類妆偏。 該類從相應(yīng)的層A繼承刃鳄,也可以從elx :: BaseComponent繼承。 這使我們有機(jī)會向所有elastix組件添加通用接口钱骂,而不管這些ITK類繼承自哪里叔锐。 此接口的示例如下:
void BeforeAll(void)
void BeforeRegistration(void)
void BeforeEachResolution(void)
void AfterEachResolution(void)
void AfterEachIteration(void)
void AfterRegistration(void)
這些方法會在函數(shù)名稱所示的時刻自動調(diào)用。 這讓你有機(jī)會閱讀/設(shè)置一些參數(shù)见秽,打印一些輸出掌腰,保存一些結(jié)果等。
7.5 編程風(fēng)格
為了提高代碼的可讀性和一致性张吉,對可維護(hù)性有積極的影響齿梁,我們采用了編碼風(fēng)格。 自4.7版以來,elastix提供了一個粗略的uncrustify配置文件勺择。
-
White spacing 良好的間距提高了代碼的可讀性创南。 因此,
- 不要使用tabs省核。 Tabs取決于tab大小稿辙,這將使代碼顯示取決于查看器。 我們每個標(biāo)簽使用2個空格气忠。 在Visual Studio中邻储,可以設(shè)置為首選項(xiàng):轉(zhuǎn)到工具→選項(xiàng)→文本編輯器→所有語言→制表符,然后tab size=縮進(jìn)大小= 2并標(biāo)記“插入空格”旧噪。 在vim中吨娜,您可以調(diào)整您的.vimrc以包含set ts = 2; set sw = 2;
set expandtab。 - 行末沒有空格淘钟,就像ITK一樣宦赠。 這只是丑陋的。為了使它們(非常)引人注目的在.vimrc中添加以下內(nèi)容:
- 不要使用tabs省核。 Tabs取決于tab大小稿辙,這將使代碼顯示取決于查看器。 我們每個標(biāo)簽使用2個空格气忠。 在Visual Studio中邻储,可以設(shè)置為首選項(xiàng):轉(zhuǎn)到工具→選項(xiàng)→文本編輯器→所有語言→制表符,然后tab size=縮進(jìn)大小= 2并標(biāo)記“插入空格”旧噪。 在vim中吨娜,您可以調(diào)整您的.vimrc以包含set ts = 2; set sw = 2;
:highlight ExtraWhitespace ctermbg=red guibg=red
:match ExtraWhitespace /\s+$/
- 在函數(shù)米母,循環(huán)勾扭,索引等中使用空格。所以铁瞒,
FunctionName(.void.);
for(.i.=.0;.i.<.10;.++i.)
vector[.i.].=.3;
- **縮進(jìn)**
- 不要太多妙色,不要太長線(too long lines)
namespace itk
{ ^ ^
/**
.* ********************* Function ******************************
./
^
template <class TTemplate1, class TTemplate2>
void
ClassName<TTemplate1, TTemplate2>
::Function(.void.)
{
..//Function body
..this->OtherMemberFunction(.arguments.);
..for(.i.=.0;.i.<.10;.++i.)
..{
....x.+=.i..i;
..}
^
} // end Function()
^
}.//.end.namespace.itk
- 類
namespace itk
{ ^
/.\class.ClassName
..\brief.Brief.description
.
..Detailed.description
.
..\ingroup.Group
./
^
template < templateArguments >
class.ClassName:
public.SuperclassName
{
public:
^
../*.Standard.class.typedefs../
..typedef.ClassName..................Self;
..typedef.SuperclassName.............Superclass;
- **變量和函數(shù)命名** 如果從變量的名稱,你知道它是本地或一個類成員慧耍,那很好燎斩。 因此,
- 成員變量以m_為前綴蜂绎,后跟大寫。 在實(shí)現(xiàn)中使用this->引用它們笋鄙。 所以师枣,這個 this->m_MemberVariable是正確的。
- 局部變量應(yīng)以小寫字符開頭萧落。
- 函數(shù)名從大寫開始
- 使用這個 - >來調(diào)用成員函數(shù)
- **更好的代碼** 看一些簡單的事情:
- 隨時隨地使用const
- 對于浮點(diǎn)數(shù)不要使用0践美,但是0.0可以避免可能出現(xiàn)的錯誤。
- 在派生類中覆蓋虛擬函數(shù)時使用virtual關(guān)鍵字找岖。 這在C ++中不是嚴(yán)格需要的陨倡,但是當(dāng)您使用virtual關(guān)鍵字,覆蓋或意圖被覆蓋的函數(shù)會很清楚许布。
- 始終使用開啟和關(guān)閉括號兴革。 盡管C ++并不總是需要這樣做的
if(.condition.)
{
..valid = true;
}
而不是
if(.condition.)
..valid = true;
``
對于循環(huán)也應(yīng)該這樣。
- 注釋 代碼是由別人閱讀,或者是你在幾年的時間里閱讀杂曲。 所以庶艾,
- 多注釋
- 以} // end FunctionName()結(jié)束函數(shù)