7.1 與ITK的關系
elastix代碼的很大一部分是基于ITK Ib′a?nez et al[2005]。 ITK的使用意味著低級功能(圖像類,內存分配等)經過徹底測試。 當然,由ITK支持的所有圖像格式elastix也支持。 可以使用各種編譯器(MS Visual Studio,GCC)在多個操作系統(Windows XP,Linux,Mac OS X)上編譯C ++源代碼,并支持32位和64位系統。
除了現有的ITK圖像配準類之外,elastix還實現了新的功能。 最重要的增強功能列在表7.1中。 請注意,從第4版起,ITK還具有變換級聯,并支持空間導數(但不是其導數再次為μ)。
7.2 elastix代碼概述
elastix源代碼大致分為兩層,分別以C ++編寫:A)實現圖像配準功能的ITK風格的類,以及B)elastix包裝器,它兼容閱讀和設置參數,實例化和連接組件,保存(中間) 結果和類似的“行政”任務。 模塊化設計可以添加新的組件,而無需更改elastix core。 添加一個新組件首先創建一個層A類,可以獨立于B層進行編譯和測試。接下來,需要寫一個小B層包裝,將A類與elastix的其他部分連接起來。
例如,圖像采樣器被實現為所有從基類itk :: ImageSamplerBase繼承的ITK類。 這些可以在src / Common / ImageSamplers中找到。 這是elastix的“層A”。 對于每個采樣器(隨機,網格,完整...),寫入一個包裝器,位于src / Components / ImageSamplers中,它在配準過程的每個新分辨率之前都需要配置采樣器。 這是elastix的“B層”。
7.2.1 目錄結構
基本目錄結構如下:
- dox
- src/Common: ITK類,Layer A stuff。 該目錄還包含一些與ITK無關的外部庫,如xout(由我們編寫)和ANNlib。
- src/Core: 這是主要的elasticix內核,負責執行流程,連接類,讀取參數等。
- 抽樣策略的模塊化框架。See for more details Staring and
Klein [2010b]. - 幾個新的優化者:Kiefer-Wolfowitz,Robbins-Monro,自適應隨機梯度下降,進化策略。 對現有ITK優化器進行完整的返工,增加用戶控制和更好的錯誤處理:準牛頓,非線性共軛梯度。
- 幾個新的或更靈活的成本函數:(標準化)互信息,用Parzen窗口實現,類似于Th'evenaz and Unser [2000],多重α相互信息,彎曲能量懲罰項,剛性懲罰項。
- 連接任意數量幾何變換的能力。
- 轉換不僅支持?T/?μ的計算,而且還支持空間導數?T/?x和?2T/?x2的計算,并且它們的導數為μ的計算,用于計算正則化項時經常需要。 另外,更一般地集成了某些轉換的緊湊支持。 更多詳情查看Staring and Klein [2010a]。
- 成本函數的線性組合,而不是單一的成本函數。
- 抽樣策略的模塊化框架。See for more details Staring and
表7.1:與ITK相比,elastix的最重要的增強和增加
- src/Components: 此目錄包含組件及其elastix包裝器(B層)。 非常特定的組件A層代碼也可以在這里找到。
在elastix 4.4和更高版本中,還可以添加您自己的組件目錄。 這些可以位于elastix源樹之外的任何地方。 有關詳細信息,請參見第7.4節。
7.3 在自己的軟件中使用elastix
在自己的軟件中有(至少)三種使用彈性的方式:
- 編譯elastix可執行文件,并直接用適當的參數調用它。 這是最簡單的方法,Matlab和MeVisLab代碼存在。
- 將elastix源代碼包含在您自己的項目中,請參見第7.3.1節。
- 將elastix編譯為庫并鏈接到該庫,請參見第7.3.2節。 此功能從版本4.7(2014年2月)起可用。
7.3.1 在您自己的軟件中包括elasticix代碼
您可能會發現一些elastix類有助于集成到您自己的項目中。 例如,如果您正在開發一個新的elasticix組件,并且首先要在elasticix之外測試(參見第7.4節),在這種情況下,您當然可以將所需的elastix文件復制到您自己的項目中,或者手動設置包含路徑,但這不會很方便。
為了更容易,在elastix二進制目錄中生成一個UseElastix.cmake文件。 您可以將其包含在您自己的項目的CMakeLists.txt文件中,并且CMake將確保設置了所有必需的包含目錄。 此外,您可以鏈接到elastix庫,如elxCommon,以避免重新編譯代碼。
一個例子可以在elastix source distribution的目錄dox / externalproject中找到。
7.3.2 使用elastix作為庫
簡介
elastix還提供了用作動態或靜態鏈接庫的可能性。 這提供了將其功能集成到您自己的軟件中的可能性,而無需調用外部的elasticix可執行文件。 后者的缺點是,您的軟件(可能已經在內存中已經有固定和運動的圖像)必須將這些圖像存儲到磁盤。 然后,elastix將再次加載它們(因此它們將在內存中兩次),執行配準,并將結果寫入磁盤。 然后,您的軟件需要將結果從磁盤加載到內存。 這種方法顯然導致內存使用的增加,由于讀/寫開銷而導致的性能下降,并不是非常優雅。 當使用elastix作為庫時,您的軟件只需要將存儲器指針傳遞給庫接口,因此不需要讀/寫或內存映像復制。 配準之后,elastix將會將指針返回到您的程序。
庫功能還在相當的深入開發中,但是以下基本功能已經可用:
- 使用elastix組件的任何組合配準任何一對圖像。
- 配準掩碼(masks)的使用
- 連續使用多個參數文件(類似于使用elastix可執行文件的-p選項多次)。
- 使用transformix來轉換圖像。
將elastix作為靜態或動態庫構建
要構建elastix庫作為庫,您必須禁用CMake中的ELASTIX_BUILD_ EXECUTABLE選項。 使用此選項禁用一個構建項目,將創建一個靜態庫。 如果要創建動態庫(測試不夠好),則必須啟用ELASTIX_BUILD_SHARED_ LIBS選項。
與elastix庫連接
在構建自己的軟件項目時,需要將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添加一個參數,ELASTIX_BUILD_DIR,需要在運行CMake時由用戶提供。 您應該提供您編譯elastix源代碼的目錄(具有UseElastix.cmake文件的目錄)。 如果要更好地控制鏈接elastix的二進制文件,請使用CMaketarget_link_libraries指令。
準備配準參數設置
要能夠運行elastix,您需要首先準備參數設置。 例如你可以通過從文件中讀取它們來做到這一點:
#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();
如果要連續使用多個參數文件,請逐個加載它們,并將它們添加到向量中:
typedef std::vector<RegistrationParametersType> RegistrationParametersContainerType;
然后在下面的代碼中使用此向量,而不是單個參數映射。 您還可以在C ++代碼中設置參數映射。 檢查ELASTIX :: ParameterMapType的typedef的確切格式。
運行elastix
加載參數設置后,使用例如以下代碼運行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;
運行transformix
由ELASTIX類提供的轉換參數,您可以運行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;
或者,您可以使用參數文件解析器從文件(例如,從TransformParameters.0.txt)讀取轉換參數,方法與上述配準參數所示相同。
7.4 創建新組件
如果要創建自己的組件,開始編寫A層類是很自然的,而不用擔心elastix。 A層過濾器應該實現所有基本功能,并且可以在單獨的ITK程序中進行測試,如果它執行了應該做的事情。 一旦你獲得了這個ITK類的工作,在elastix文件(從現有組件復制粘貼開始)中編寫B層包裝很簡單。
使用CMake,您可以通過使用“ELASTIX_USER_COMPONENT_DIRS”選項告訴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中定義的宏。 第一個參數是B層包裝類的名稱,它在“elxAdvancedMeanSquaresMetric.h”中聲明。 之后,您可以指定組件所依賴的源文件。 在上面的例子中,以“itk”開頭的文件形成了A層代碼。 以“elx”開頭的文件是B層代碼。 文件“elxAdvancedMeanSquaresMetric.cxx”特別簡單。 它只包括兩行:
#include "elxAdvancedMeanSquaresMetric.h"
elxInstallMacro( AdvancedMeanSquaresMetric );
elxInstallMacro在src / Core / Install / elxMacro.h中定義。
文件elxAdvancedMeanSquaresMetric.h / hxx一起定義了B層包裝類。 該類從相應的層A繼承,也可以從elx :: BaseComponent繼承。 這使我們有機會向所有elastix組件添加通用接口,而不管這些ITK類繼承自哪里。 此接口的示例如下:
void BeforeAll(void)
void BeforeRegistration(void)
void BeforeEachResolution(void)
void AfterEachResolution(void)
void AfterEachIteration(void)
void AfterRegistration(void)
這些方法會在函數名稱所示的時刻自動調用。 這讓你有機會閱讀/設置一些參數,打印一些輸出,保存一些結果等。
7.5 編程風格
為了提高代碼的可讀性和一致性,對可維護性有積極的影響,我們采用了編碼風格。 自4.7版以來,elastix提供了一個粗略的uncrustify配置文件。
-
White spacing 良好的間距提高了代碼的可讀性。 因此,
- 不要使用tabs。 Tabs取決于tab大小,這將使代碼顯示取決于查看器。 我們每個標簽使用2個空格。 在Visual Studio中,可以設置為首選項:轉到工具→選項→文本編輯器→所有語言→制表符,然后tab size=縮進大小= 2并標記“插入空格”。 在vim中,您可以調整您的.vimrc以包含set ts = 2; set sw = 2;
set expandtab。 - 行末沒有空格,就像ITK一樣。 這只是丑陋的。為了使它們(非常)引人注目的在.vimrc中添加以下內容:
- 不要使用tabs。 Tabs取決于tab大小,這將使代碼顯示取決于查看器。 我們每個標簽使用2個空格。 在Visual Studio中,可以設置為首選項:轉到工具→選項→文本編輯器→所有語言→制表符,然后tab size=縮進大小= 2并標記“插入空格”。 在vim中,您可以調整您的.vimrc以包含set ts = 2; set sw = 2;
:highlight ExtraWhitespace ctermbg=red guibg=red
:match ExtraWhitespace /\s+$/
- 在函數,循環,索引等中使用空格。所以,
FunctionName(.void.);
for(.i.=.0;.i.<.10;.++i.)
vector[.i.].=.3;
- **縮進**
- 不要太多,不要太長線(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;
- **變量和函數命名** 如果從變量的名稱,你知道它是本地或一個類成員,那很好。 因此,
- 成員變量以m_為前綴,后跟大寫。 在實現中使用this->引用它們。 所以,這個 this->m_MemberVariable是正確的。
- 局部變量應以小寫字符開頭。
- 函數名從大寫開始
- 使用這個 - >來調用成員函數
- **更好的代碼** 看一些簡單的事情:
- 隨時隨地使用const
- 對于浮點數不要使用0,但是0.0可以避免可能出現的錯誤。
- 在派生類中覆蓋虛擬函數時使用virtual關鍵字。 這在C ++中不是嚴格需要的,但是當您使用virtual關鍵字,覆蓋或意圖被覆蓋的函數會很清楚。
- 始終使用開啟和關閉括號。 盡管C ++并不總是需要這樣做的
if(.condition.)
{
..valid = true;
}
而不是
if(.condition.)
..valid = true;
``
對于循環也應該這樣。
- 注釋 代碼是由別人閱讀,或者是你在幾年的時間里閱讀。 所以,
- 多注釋
- 以} // end FunctionName()結束函數