吐槽一下
先吐槽一下,网络上能找到的例子基本上都是基于官网的例子,官网链接 。偶尔找到质量高点的,但是不按照他给出的代码原样照搬依然有问题(因为要封装就要解耦,一旦解耦就出问题😒)。在实际开发中,特别是在同别的接口对接的时候很少会直接从硬盘加载数据,都是直接接收调用者给出的内存数据,或者直接处理后给出处理结果(你要是让人家先写硬盘,你再从硬盘加载,或者反过来,我敢保证你老大不打死你也得骂死你😂)
流程
- 利用itk::ImportImageFilter从内存中读取数据流,生成itk::Image;
- 使用itk自带的itk::ResampleImageFilter实现重采样;
- 从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()操作!!!!)
-
调用示例
-
读取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; }
-
从内存加载数据并重采样
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文件中的值,在实际使用中根据实际情况修改!
-