ITK简单封装-实现重采样

吐槽一下

先吐槽一下,网络上能找到的例子基本上都是基于官网的例子,官网链接 。偶尔找到质量高点的,但是不按照他给出的代码原样照搬依然有问题(因为要封装就要解耦,一旦解耦就出问题😒)。在实际开发中,特别是在同别的接口对接的时候很少会直接从硬盘加载数据,都是直接接收调用者给出的内存数据,或者直接处理后给出处理结果(你要是让人家先写硬盘,你再从硬盘加载,或者反过来,我敢保证你老大不打死你也得骂死你😂)

流程

  1. 利用itk::ImportImageFilter从内存中读取数据流,生成itk::Image;
  2. 使用itk自带的itk::ResampleImageFilter实现重采样;
  3. 从ResampleImageFilter中得到itk::Image,进而得到数据的内存地址;

实现过程

  • 配置ITK开发环境,这个网上很多,讲的都挺好,按部就班的做就是了,下载->CMAKE->vs编译->install
  • 在工程中引入itk的*.lib和各种头文件
  • 新建ITKHelper.h文件
    ITKHelper.h
#pragma once

#define BEGIN_NS_ITK namespace NS_ITK{
#define END_NS_ITK   }

#include "itkImage.h" 
#include "itkImportImageFilter.h"
#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include "itkLinearInterpolateImageFunction.h"
#include "itkNearestNeighborInterpolateImageFunction.h"
#include "itkResampleImageFilter.h"
#include "itkScaleTransform.h"
#include "itkMetaImageIO.h"
#include <itkNiftiImageIO.h>

using namespace std;

BEGIN_NS_ITK

const unsigned int Dimension = 3;
using PixelType = short;
using ImageType = itk::Image<PixelType, Dimension>;
using ScalarType = double;
using IndexValueType = itk::Index<Dimension>::IndexValueType;
using ResampleFilterType = itk::ResampleImageFilter<ImageType, ImageType>;
using ImportFilterType = itk::ImportImageFilter< PixelType, Dimension > ;

class ITKHelper
{
private:
	ResampleFilterType::Pointer resampleFilter;
	ImportFilterType::Pointer importFilter;

public:
	ITKHelper();
	bool SaveNii(ImageType::Pointer itkImage, const char * outputFileName);
	ImageType::Pointer ReadMHD(const char * inputFileName);	
	ImageType::Pointer ReadFromBuffer(unsigned int* sizeArry, double* spacing, double* origin, PixelType* data);	
	ImageType::Pointer Resample(ImageType::Pointer itkImageInput, double outputSpacingArry[3]);
};

END_NS_ITK

解释一下:

  • ReadMHD()函数是从硬盘读取*.mhd文件,这里仅仅作为一个例子,基本上医学常用的类型itk都可以读取,唯一需要注意的地方是需要把对应的"itkxxxIO.h"包含进来,在实现的时候加上reader->SetImageIO(),不然就会报错,网上的例子也不知道他们到底有没有运行通过,这里基本上就是清一色的直接

    reader->SetFileName(inputFileName);
    reader->Update();
    

    包括保存的时候也一样需要设置一下格式

    writer->SetImageIO(niiIO);
    

    不然就是没有注册xxx factory的错误,你可以先不加试试。

  • ITKHelper.cpp

    #include "ITKHelper.h"
    
    NS_ITK::ITKHelper::ITKHelper()
    {
    	resampleFilter = ResampleFilterType::New();
    	importFilter = ImportFilterType::New();
    }
    
    BEGIN_NS_ITK
    
    ImageType::Pointer ITKHelper::ReadMHD(const char * inputFileName)
    {
    	using ReaderType = itk::ImageFileReader<ImageType>;
    	ReaderType::Pointer reader = ReaderType::New();
    
    	typedef itk::MetaImageIO             ImageIOType;
    	ImageIOType::Pointer metaIO = ImageIOType::New();
    	reader->SetImageIO(metaIO);
    	try
    	{
    		reader->SetFileName(inputFileName);
    		reader->Update();
    		cout << "Read success!" << endl;		
    	}
    
    	catch (itk::ExceptionObject & error)
    	{
    		std::cerr << "Error: " << error << std::endl;
    		return nullptr;
    	}
    	return reader->GetOutput();
    }
    
    ImageType::Pointer ITKHelper::Resample(ImageType::Pointer itkImageInput, double outputSpacingArry[3])
    {
    	const ImageType::RegionType  inputRegion = itkImageInput->GetLargestPossibleRegion();
    	const ImageType::SizeType    inputSize = inputRegion.GetSize();
    	const ImageType::SpacingType inputSpacing = itkImageInput->GetSpacing();
    	const ImageType::PointType   inputOrigin = itkImageInput->GetOrigin();
    
    	// 重采样后的spacing
    	ImageType::SpacingType outputSpacing;
    	for (int i = 0; i < Dimension; i++)
    	{
    		outputSpacing[i] = outputSpacingArry[i];
    	}
    
    	// 重采样后的图像大小
    	ImageType::SizeType    outputSize;
    	outputSize[0] = inputSize[0] * inputSpacing[0] / outputSpacing[0];
    	outputSize[1] = inputSize[1] * inputSpacing[1] / outputSpacing[1];
    	outputSize[2] = inputSize[2] * inputSpacing[2] / outputSpacing[2];
    
    
    	ImageType::PointType   outputOrigin = inputOrigin;
    	using NearestNeighborInterpolatorType = itk::NearestNeighborInterpolateImageFunction<ImageType, ScalarType>;
    	NearestNeighborInterpolatorType::Pointer nearestNeighborInterpolator = NearestNeighborInterpolatorType::New();
    
    	resampleFilter->SetInput(itkImageInput);
    	resampleFilter->SetInterpolator(nearestNeighborInterpolator);
    	resampleFilter->SetSize(outputSize);
    	resampleFilter->SetOutputSpacing(outputSpacing);
    	resampleFilter->SetOutputOrigin(outputOrigin);
    	resampleFilter->SetOutputDirection(itkImageInput->GetDirection());
    	resampleFilter->Update();
    	cout << "Resample success!" << endl;
    	return resampleFilter->GetOutput();
    }
    
    bool ITKHelper::SaveNii(ImageType::Pointer itkImage, const char * outputFileName)
    {
    	// 保存结果
    	using WriterType = itk::ImageFileWriter<ImageType>;
    	WriterType::Pointer writer = WriterType::New();
    	using ImageIOTypeNII = itk::NiftiImageIO;
    	ImageIOTypeNII::Pointer niiIO = ImageIOTypeNII::New();
    	writer->SetImageIO(niiIO);
    	writer->SetFileName(outputFileName);
    	writer->SetInput(itkImage);
    
    	try
    	{
    		writer->Update();
    		cout << outputFileName << "save success!" << endl;
    	}
    	catch (itk::ExceptionObject & error)
    	{
    		std::cerr << "Error: " << error << std::endl;
    		return EXIT_FAILURE;
    	}
    
    	return EXIT_SUCCESS;
    }
    
    ImageType::Pointer ITKHelper::ReadFromBuffer(unsigned int* sizeArry, double* spacingArry, double* originArry, PixelType* data)
    {
    	ImportFilterType::SizeType size;
    	for (int i = 0; i < Dimension; i++)
    	{
    		size[i] = sizeArry[i];
    	}
    
    	ImportFilterType::IndexType start;
    	start.Fill(0);
    	ImportFilterType::RegionType region;
    	region.SetIndex(start);
    	region.SetSize(size);
    	importFilter->SetRegion(region);
    
    	//使用SetOrigin()方法指定输出图像的原点。
    	ImageType::PointType   origin;
    	for (int i = 0; i < Dimension; i++)
    	{
    		origin[i] = originArry[i];
    	}
    	importFilter->SetOrigin(origin);
    
    	//图像的间距通过SetSpacing()方法传递。
    	ImageType::SpacingType spacing;
    	for (int i = 0; i < Dimension; i++)
    	{
    		spacing[i] = spacingArry[i];
    	}
    	importFilter->SetSpacing(spacing);
    
    	const unsigned int numberOfPixels = size[0] * size[1] * size[2];
    	const bool importImageFilterWillOwnTheBuffer = true;
    	importFilter->SetImportPointer(data, numberOfPixels, importImageFilterWillOwnTheBuffer);
    	importFilter->Update();
    	return importFilter->GetOutput();
    }
    END_NS_ITK
    

    解释一下注意事项:

    • itk的使用过程其实不难,主要是找到对应的类,然后进行一系列的设置,最后update() update() update(),重要的事情说三遍
    • 最后的update()千万别忘了!!!不论是读取的reader还是写入的writer又或者是那两个filter,都需要update(),不然你会发现都设置好后GetOutput()就是没数据(我就是这里被网上的代码坑死了,他们要么就如同官网那样直接writer->SetInput(xx->GetOutput())写磁盘了,要么就不写也不说,搞的我郁闷死了,数据在filter中都对,就是GetOutput()后什么都木有!!就是因为没有执行update()操作!!!!)
  • 调用示例

    1. 读取mdh文件,并保存成nii.gz, 数据文件保存成xxx.npy二进制文件

      #include "stdafx.h"
      #include "ITKHelper.h"
      #include <fstream>
      #include <iostream>
      #include <sstream>
      
      
      #pragma warning(disable:4996)
      
      using namespace std;
      using namespace NS_ITK;
      
      int ReadTest()
      {
      	const char * inputFileName = "F:\\code\\Data\\MHDData\\case_003\\leg.mhd";
      	const char * outputFileName = "f:\\case_003.nii.gz";
      
      	ITKHelper handler;
      	ImageType::Pointer itkImageInput = handler.ReadMHD(inputFileName);
      	const ImageType::SizeType data_size = itkImageInput ->GetLargestPossibleRegion().GetSize();
      	short* raw_data = itkImageInput->GetBufferPointer();	
      	handler.SaveNii(itkImageInput , outputFileName);
      
      	 保存成二进制文件	
      	ofstream fout("f:\\case_003.npy", ios::binary | ios::out);	
      	fout.write((char*)raw_data , data_size[0] * data_size[1] * data_size[2] * sizeof(short));
      	fout.close();
      	return 0;
      }
      
    2. 从内存加载数据并重采样

      int resample()
      {	
      	const char * inputFileName = "f:\\case_003.npy";
      	const char * outputFileName= "f:\\case_003_resmaple.nii.gz";
      
      	ITKHelper handler;	
      	short* data = new short[512 * 512 * 1451];
      	ifstream in(inputFileName, ios::in | ios::binary);
      	in.read((char*)data, 512 * 512 * 1451 * sizeof(short));
      	
      	unsigned int sizeArry[3] = { 512, 512, 1451 };
      	double spacingArry[3] = { 0.552734, 0.552734, 0.6 };
      	double originArry[3] = { -141.224, - 280.224, - 884.1 };
      
      	ImageType::Pointer itkImageInput = handler.ReadFromBuffer(sizeArry, spacingArry, originArry, data);
      	ImageType::Pointer resampleItkImage = handler.Resample(itkImageInput, new double[3]{1.0,1.0,1.0});
      	handler.SaveNii(resampleItkImage, outputFileName);
      	return 0;
      
      }
      

      说明一下:代码中出现的参数值都应该从原始的mhd文件或dicom文件中获取,或者由调用者提供,代码中的各个值是我的mhd文件中的值,在实际使用中根据实际情况修改!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值