Categories: C/C++

ITK 有限元素法影像對準 FEM Registration 教學與範例

介紹 ITK 所提供的有限元素法影像對準的使用方式,並提供基本的範例程式碼。

有限元素法影像對準

以下是使用 ITK 的有限元素法(finite element method,簡稱 FEM)影像對準的基礎範例。

#include <itkImageFileReader.h>
#include <itkImageFileWriter.h>
#include <itkRescaleIntensityImageFilter.h>
#include <itkHistogramMatchingImageFilter.h>
#include <itkFEMRegistrationFilter.h>
#include <itkCheckerBoardImageFilter.h>

int main(int argc, char * argv[]) {
  const char *fixedImageName, *movingImageName;
  if (argc < 2) {
    std::cout << "Usage: " << argv[0] << " fixedImageFile movingImageFile"
              << std::endl;
    return EXIT_FAILURE;
  } else {
    fixedImageName = argv[1];
    movingImageName = argv[2];
  }

  // 定義適用於 2D FEM 影像對準的型別
  using DiskImageType = itk::Image<unsigned char, 2>;
  using ImageType = itk::Image<float, 2>;
  using ElementType = itk::fem::Element2DC0LinearQuadrilateralMembrane;
  using FEMObjectType = itk::fem::FEMObject<2>;
  using RegistrationType =
    itk::fem::FEMRegistrationFilter<ImageType, ImageType, FEMObjectType>;

  // 建立 FEMRegistrationFilter,並設定各種參數
  RegistrationType::Pointer registrationFilter = RegistrationType::New();
  registrationFilter->SetMaxLevel(1);
  registrationFilter->SetUseNormalizedGradient(true);
  registrationFilter->ChooseMetric(0);

  unsigned int maxiters = 20;
  float        E = 100;
  float        p = 1;
  registrationFilter->SetElasticity(E, 0);
  registrationFilter->SetRho(p, 0);
  registrationFilter->SetGamma(1., 0);
  registrationFilter->SetAlpha(1.);
  registrationFilter->SetMaximumIterations(maxiters, 0);
  registrationFilter->SetMeshPixelsPerElementAtEachResolution(4, 0);
  registrationFilter->SetWidthOfMetricRegion(1, 0);
  registrationFilter->SetNumberOfIntegrationPoints(2, 0);
  registrationFilter->SetDoLineSearchOnImageEnergy(0);
  registrationFilter->SetTimeStep(1.);
  registrationFilter->SetEmployRegridding(false);
  registrationFilter->SetUseLandmarks(false);

  // 讀取影像
  using FileSourceType = itk::ImageFileReader<DiskImageType>;

  FileSourceType::Pointer movingfilter = FileSourceType::New();
  movingfilter->SetFileName(movingImageName);
  FileSourceType::Pointer fixedfilter = FileSourceType::New();
  fixedfilter->SetFileName(fixedImageName);
  movingfilter->Update();
  fixedfilter->Update();

  // 將影像數值範圍調整為 0255
  using FilterType =
    itk::RescaleIntensityImageFilter<DiskImageType, ImageType>;
  FilterType::Pointer movingrescalefilter = FilterType::New();
  FilterType::Pointer fixedrescalefilter = FilterType::New();

  movingrescalefilter->SetInput(movingfilter->GetOutput());
  fixedrescalefilter->SetInput(fixedfilter->GetOutput());

  constexpr double desiredMinimum = 0.0;
  constexpr double desiredMaximum = 255.0;

  movingrescalefilter->SetOutputMinimum(desiredMinimum);
  movingrescalefilter->SetOutputMaximum(desiredMaximum);
  movingrescalefilter->UpdateLargestPossibleRegion();
  fixedrescalefilter->SetOutputMinimum(desiredMinimum);
  fixedrescalefilter->SetOutputMaximum(desiredMaximum);
  fixedrescalefilter->UpdateLargestPossibleRegion();

  // 以直方圖分布的方式標準化影像值分布
  using HEFilterType =
    itk::HistogramMatchingImageFilter<ImageType, ImageType>;
  HEFilterType::Pointer IntensityEqualizeFilter = HEFilterType::New();

  IntensityEqualizeFilter->SetReferenceImage(fixedrescalefilter->GetOutput());
  IntensityEqualizeFilter->SetInput(movingrescalefilter->GetOutput());
  IntensityEqualizeFilter->SetNumberOfHistogramLevels(100);
  IntensityEqualizeFilter->SetNumberOfMatchPoints(15);
  IntensityEqualizeFilter->ThresholdAtMeanIntensityOn();
  IntensityEqualizeFilter->Update();

  // 設定 FEMRegistrationFilter 的固定影像與調動影像
  registrationFilter->SetFixedImage(fixedrescalefilter->GetOutput());
  registrationFilter->SetMovingImage(IntensityEqualizeFilter->GetOutput());

  //  In order to initialize the mesh of elements, we must first create
  //  ``dummy'' material and element objects and assign them to the
  //  registration filter.  These objects are subsequently used to
  //  either read a predefined mesh from a file or generate a mesh using
  //  the software.  The values assigned to the fields within the
  //  material object are arbitrary since they will be replaced with
  //  those specified earlier.  Similarly, the element
  //  object will be replaced with those from the desired mesh.

  // 建立材質性質
  itk::fem::MaterialLinearElasticity::Pointer m;
  m = itk::fem::MaterialLinearElasticity::New();
  m->SetGlobalNumber(0);
  m->SetYoungsModulus(registrationFilter->GetElasticity()); // 楊氏模數
  m->SetCrossSectionalArea(1.0);
  m->SetThickness(1.0);
  m->SetMomentOfInertia(1.0);
  m->SetPoissonsRatio(0.); // 蒲松比(不要設 1.0
  m->SetDensityHeatProduct(1.0);

  // 建立 ElementType
  ElementType::Pointer e1 = ElementType::New();
  e1->SetMaterial(static_cast<itk::fem::Material::ConstPointer>(m));
  registrationFilter->SetElement(static_cast<itk::fem::Element::Pointer>(e1));
  registrationFilter->SetMaterial(m);

  // 進行影像對準
  registrationFilter->RunRegistration();

  // 儲存對準後的影像
  itk::ImageFileWriter<ImageType>::Pointer warpedImageWriter;
  warpedImageWriter = itk::ImageFileWriter<ImageType>::New();
  warpedImageWriter->SetInput(registrationFilter->GetWarpedImage());
  warpedImageWriter->SetFileName("warpedMovingImage.mha");
  warpedImageWriter->Update();

  // 儲存 displacement field
  using DispWriterType = itk::ImageFileWriter<RegistrationType::FieldType>;
  DispWriterType::Pointer dispWriter = DispWriterType::New();
  dispWriter->SetInput(registrationFilter->GetDisplacementField());
  dispWriter->SetFileName("displacement.mha");
  dispWriter->Update();

  // 將輸出影像轉型為指定類型
  using OutputImageType = itk::Image<unsigned char, 2>;
  using CastFilterType =
    itk::CastImageFilter<ImageType, OutputImageType>;
  using WriterType = itk::ImageFileWriter<OutputImageType>;

  WriterType::Pointer writer = WriterType::New();
  CastFilterType::Pointer caster = CastFilterType::New();

  // 產生棋盤式影像比較圖
  using CheckerBoardFilterType = itk::CheckerBoardImageFilter<ImageType>;
  CheckerBoardFilterType::Pointer checker = CheckerBoardFilterType::New();

  // 輸出影像對準前的棋盤式影像比較圖
  checker->SetInput1(registrationFilter->GetFixedImage());
  checker->SetInput2(registrationFilter->GetMovingImage());
  caster->SetInput(checker->GetOutput());
  writer->SetInput(caster->GetOutput());
  writer->SetFileName("before.png");
  writer->Update();

  // 輸出影像對準後的棋盤式影像比較圖
  checker->SetInput1(registrationFilter->GetFixedImage());
  checker->SetInput2(registrationFilter->GetWarpedImage());
  caster->SetInput(checker->GetOutput());
  writer->SetInput(caster->GetOutput());
  writer->SetFileName("after.png");
  writer->Update();

  return EXIT_SUCCESS;
}

將這個 C++ 程式儲存為 FEMReg.cxx 之後,搭配以下 CMakeLists.txt 設定檔以 CMake 編譯。

cmake_minimum_required(VERSION 3.10.2)

# 設定專案名稱
project(FEMReg)

# 尋找並引入 ITK 函式庫
find_package(ITK REQUIRED)
include(${ITK_USE_FILE})

# 增加一個執行檔
add_executable(FEMReg FEMReg.cxx)

# 定義執行檔連結方式
target_link_libraries(FEMReg ${ITK_LIBRARIES})

編譯與執行的指令如下:

# 編譯程式
mkdir build
cd build
cmake ..

# 執行程式
./FEMReg RatLungSlice1.mha RatLungSlice2.mha

這裡的 RatLungSlice1.mhaRatLungSlice2.mha 圖檔可以從 ITK 的 GitHub 網站取得,執行後除了可以產生對準後的圖形之外,也可以產生影像對準前後的棋盤式影像比較圖以及 displacement field。

影像對準前後棋盤式影像比較圖

這裡產生的 displacement.mha 可以使用以下 Python 程式繪製圖形:

import SimpleITK as sitk
import matplotlib.pyplot as plt
import numpy as np

displacement = sitk.ReadImage("displacement.mha")
movingImage = sitk.ReadImage("moving.mha")

dispNDA = sitk.GetArrayViewFromImage(displacement)
movingNDA = sitk.GetArrayViewFromImage(movingImage)

ds = 4
X = np.arange(0, movingNDA.shape[0], ds)
Y = np.arange(0, movingNDA.shape[1], ds)
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot()
ax.imshow(movingNDA, cmap='gray')
ax.quiver(X, Y, dispNDA[::ds,::ds,0], dispNDA[::ds,::ds,1], color='r', units='xy', scale=1)
plt.show()
Displacement Field

參考資料:DeformableRegistration1.cxx

Share
Published by
Office Guide
Tags: ITK

Recent Posts

Python 使用 PyAutoGUI 自動操作滑鼠與鍵盤

本篇介紹如何在 Python ...

9 個月 ago

Ubuntu Linux 以 WireGuard 架設 VPN 伺服器教學與範例

本篇介紹如何在 Ubuntu ...

9 個月 ago

Linux 網路設定 ip 指令用法教學與範例

本篇介紹如何在 Linux 系...

9 個月 ago

Windows 使用 TPM 虛擬智慧卡保護 SSH 金鑰教學與範例

本篇介紹如何在 Windows...

10 個月 ago

Linux 以 Shamir’s Secret Sharing 分割保存金鑰教學與範例

介紹如何在 Linux 中使用...

11 個月 ago

Linux 以 Cryptsetup、LUKS 加密 USB 隨身碟教學與範例

介紹如何在 Linux 系統中...

11 個月 ago