美文网首页
第七章 开发者指南

第七章 开发者指南

作者: peterpan_hai | 来源:发表于2017-05-12 17:22 被阅读0次

    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]。
      • 成本函数的线性组合,而不是单一的成本函数。

    表7.1:与ITK相比,elastix的最重要的增强和增加

    • src/Components: 此目录包含组件及其elastix包装器(B层)。 非常特定的组件A层代码也可以在这里找到。

    在elastix 4.4和更高版本中,还可以添加您自己的组件目录。 这些可以位于elastix源树之外的任何地方。 有关详细信息,请参见第7.4节。

    7.3 在自己的软件中使用elastix

    在自己的软件中有(至少)三种使用弹性的方式:

    1. 编译elastix可执行文件,并直接用适当的参数调用它。 这是最简单的方法,Matlab和MeVisLab代码存在。
    2. 将elastix源代码包含在您自己的项目中,请参见第7.3.1节。
    3. 将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中添加以下内容:

    :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()结束函数

    相关文章

      网友评论

          本文标题:第七章 开发者指南

          本文链接:https://www.haomeiwen.com/subject/weoztxtx.html