FusionHelper.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. #include "FusionHelper.h"
  2. /// <summary>
  3. /// 构造函数
  4. /// </summary>
  5. FusionHelper::FusionHelper()
  6. {
  7. _fixedImage = VolumeImageType::New();
  8. _movingImage = VolumeImageType::New();
  9. _fixedImageImpporter = ImportFilterType::New();
  10. _movingImageImporter = ImportFilterType::New();
  11. _spacing = 0;
  12. _colorType = Gray8;
  13. }
  14. /// <summary>
  15. /// 析构函数
  16. /// </summary>
  17. FusionHelper::~FusionHelper()
  18. {
  19. }
  20. /// <summary>
  21. /// 读入基础体数据
  22. /// </summary>
  23. /// <param name="baseVolumeInfo"></param>
  24. bool FusionHelper::LoadBaseVolumeData(UniformVolumeDataInfo baseVolumeInfo)
  25. {
  26. try
  27. {
  28. _spacing = baseVolumeInfo.spacing;
  29. _colorType = baseVolumeInfo.colorType;
  30. _fixedImageImpporter = CreateITKImageFromImportVolumeDatas(baseVolumeInfo,true);
  31. _fixedImage = _fixedImageImpporter->GetOutput();
  32. _fixedImage->Update();
  33. #if ENABLE_OBSERVE
  34. WriteMhaImage(_fixedImage, "fixedImage");
  35. #endif
  36. return true;
  37. }
  38. catch (const std::exception& ex)
  39. {
  40. ErrorMsg::SetErrorMsg(FusionError, { ex.what() });
  41. return false;
  42. }
  43. }
  44. /// <summary>
  45. /// 将已有的体数据与另一个融合在一起
  46. /// </summary>
  47. /// <param name="anotherVolumeInfo"></param>
  48. bool FusionHelper::FuseWithAnotherVolumeData( UniformVolumeDataInfo anotherVolumeInfo)
  49. {
  50. try
  51. {
  52. // 判断spacing是否一致
  53. if (anotherVolumeInfo.spacing != _spacing)
  54. {
  55. char strMsgBuff[32];
  56. std::snprintf(strMsgBuff, 32, "unexpected spacing value ( %f ) ", anotherVolumeInfo.spacing);
  57. ErrorMsg::SetErrorMsg(FusionError, { strMsgBuff });
  58. return false;
  59. }
  60. // 创建移动图像
  61. _movingImageImporter = CreateITKImageFromImportVolumeDatas(anotherVolumeInfo);
  62. _movingImage = _movingImageImporter->GetOutput();
  63. _movingImage->Update();
  64. #if ENABLE_OBSERVE
  65. WriteMhaImage(_movingImage, "movingImage");
  66. #endif
  67. //// 第一阶段配准,平移变换,归一化互相关测度,将两个体数据在Z轴对齐
  68. auto parameters3d = Get3DTransformationsWithCorrelationMetrics();
  69. const double translation3dX = parameters3d[0];
  70. const double translation3dY = parameters3d[1];
  71. const double translation3dZ = parameters3d[2];
  72. //const double translation3dX = -0.28574895842683723;
  73. //const double translation3dY = 0.27410058291910605;
  74. //const double translation3dZ = 0.055393289309434761;
  75. ////第二阶段配准。根据第一阶段得到的Z轴偏移量,找到左右两侧体数据的Z轴中心位置附近的断面2D图像
  76. ////2D平移变换 作为变换在X-O-Y平面内配准
  77. ////采用梯度差值测度 Gradient Difference,该测度在旧的ITK配准框架里
  78. auto parameter2d = Get2DTransformationsWithGradientDifferenceMetric(translation3dZ);
  79. const double translation2dX = parameter2d[0];
  80. const double translation2dY = parameter2d[1];
  81. //const double translation2dX = 0.38425032458639652;
  82. //const double translation2dY = -0.0059800695074633745;
  83. // ToDo 观察拼接缝隙处
  84. // 对3D体数据进行重采样
  85. // 根据配准计算得到的变换矩阵,对移动图像进行重采样,此时两个体数据在世界坐标对齐
  86. // 注:配准过程是在世界坐标系下进行的,每进行一次变换,图像中的每个像素都按照矩阵M在世界坐标系中做一次重采样
  87. // 一般情况下,是直接将移动图像根据计算得到的变换矩阵进行变换,然后将其与固定图像拼在一起即可
  88. // 但考虑到一般图像的边缘处成像效果都不是很好(和皮肤接触的不够好,可能有黑色的区域),
  89. // 如果直接将移动图像进行平移后拼接,可能会在接缝处有黑色区域,影响视觉效果
  90. // 因此,这里将原本应该全部由移动图像进行的平移,分一部分给固定图像,将两个图像分别移动一部分,这样接缝处不会有黑色区域,整体视觉效果会比较好
  91. // 如果resampleFixedImage为false,则全部由移动图像完成平移\
  92. // 设置固定图像的平移初值(固定不动,全0)
  93. Transform3DType::OutputVectorType translation3dFixed;
  94. translation3dFixed[1] = 0.0;
  95. translation3dFixed[2] = 0.0;
  96. // 设置移动图像的平移初值
  97. Transform3DType::OutputVectorType translation3dMoving;
  98. // 考虑到扫查时,y方向都是贴紧皮肤的,可以认为Y方向偏移量为0
  99. translation3dMoving[1] = translation2dY;
  100. translation3dMoving[2] = translation3dZ;
  101. // 一般情况下,
  102. // 如果translation2dX>0,直接将移动图像X方向裁掉abs(translation2dX),然后将其与固定图像拼在一起即可
  103. // 如果translation2dX<0, 直接将固定图像X方向裁掉abs(translation2dX),然后将其与固定图像拼在一起即可
  104. // 但考虑到一般图像的边缘处成像效果都不是很好(和皮肤接触的不够好,可能有黑色的区域),
  105. // 如果直接将移动图像进行平移后拼接,可能会在接缝处有黑色区域,影响视觉效果
  106. // 因此,这里将原本应该全部由移动图像进行的平移,分一部分给固定图像,将两个图像分别移动一部分,这样接缝处不会有黑色区域,整体视觉效果会比较好
  107. // 如果要移动的距离太小(小于transThreshold),则不再将平移量分给两个图像分别进行,由一个图像完成即可
  108. bool resampleFixedImage = true;
  109. double transThreshold = 3 * _spacing;
  110. double absTranslation2dX = std::abs(translation2dX);
  111. if(translation2dX <0)
  112. {
  113. if(absTranslation2dX >= transThreshold)
  114. {
  115. translation3dFixed[0] = absTranslation2dX * 0.7;
  116. translation3dMoving[0] = absTranslation2dX * 0.3;
  117. }
  118. else
  119. {
  120. translation3dFixed[0] = absTranslation2dX;
  121. translation3dMoving[0] = 0;
  122. }
  123. }
  124. if(translation2dX ==0)
  125. {
  126. translation3dFixed[0] = 0;
  127. translation3dMoving[0] = 0;
  128. resampleFixedImage = false;
  129. }
  130. if(translation2dX > 0)
  131. {
  132. if(absTranslation2dX >= transThreshold)
  133. {
  134. translation3dFixed[0] = absTranslation2dX * 0.3;
  135. translation3dMoving[0] = absTranslation2dX * 0.7;
  136. }
  137. else
  138. {
  139. translation3dFixed[0] = 0;
  140. translation3dMoving[0] = absTranslation2dX;
  141. resampleFixedImage = false;
  142. }
  143. }
  144. // 分别对两幅图像进行平移变换
  145. // 平移固定图像
  146. VolumeImageType::Pointer translatedFixedImage;
  147. if(resampleFixedImage)
  148. {
  149. translatedFixedImage = Translate3DImage(_fixedImage, translation3dFixed);
  150. }
  151. else
  152. {
  153. translatedFixedImage = _fixedImage;
  154. }
  155. // 平移移动图像
  156. VolumeImageType::Pointer translatedMovingImage = Translate3DImage(_movingImage, translation3dMoving);
  157. #if ENABLE_OBSERVE
  158. WriteMhaImage(translatedFixedImage, "translatedFixImage");
  159. WriteMhaImage(translatedMovingImage, "translatedMovingImage");
  160. #endif
  161. // 将两幅图拼接在一起,拼好后的图像直接替换fixedImage(以便继续下一次拼接)
  162. // 由于固定图像在创建的时候是翻转了的,因此这里得到的拼接图像也应该是翻转的
  163. // 因此是movingImage在左,将movingImage翻转,fixedImage在右,fixedImage不翻转
  164. _fixedImageImpporter = ConcatTwoVolumeImageAlongX(translatedMovingImage, true,translatedFixedImage,false);
  165. _fixedImage = _fixedImageImpporter->GetOutput();
  166. _fixedImage->Update();
  167. #if ENABLE_OBSERVE
  168. WriteMhaImage(_fixedImage, "fusedImage");
  169. #endif
  170. return true;
  171. }
  172. catch (const std::exception& ex)
  173. {
  174. ErrorMsg::SetErrorMsg(FusionError, { ex.what() });
  175. return false;
  176. }
  177. }
  178. /// <summary>
  179. /// 得到融合后的体数据
  180. /// </summary>
  181. /// <param name="fusedVolumeInfo"></param>
  182. bool FusionHelper::GetFusedVolumeData(UniformVolumeDataInfo& fusedVolumeInfo)
  183. {
  184. try
  185. {
  186. // 将固定图像(即融合后的图像)沿着x轴翻转回来
  187. using FlipFilterType = itk::FlipImageFilter<VolumeImageType>;
  188. FlipFilterType::FlipAxesArrayType flipArray;
  189. flipArray[0] = 1;
  190. flipArray[1] = 0;
  191. flipArray[2] = 0;
  192. FlipFilterType::Pointer flipFilter = FlipFilterType::New();
  193. flipFilter->SetFlipAxes(flipArray);
  194. flipFilter->SetInput(_fixedImage);
  195. VolumeImageType::Pointer flippedImageFixed = flipFilter->GetOutput();
  196. flippedImageFixed->Update();
  197. // 将得到的图像进行转换
  198. using CastFilterType = itk::CastImageFilter<VolumeImageType, OutputImageType>;
  199. CastFilterType::Pointer caster = CastFilterType::New();
  200. caster->SetInput(flippedImageFixed);
  201. OutputImageType::Pointer outputImage= caster->GetOutput();
  202. outputImage->Update();
  203. // 将融合后的图像复制到fusedVolumeInfo里
  204. VolumeImageType::RegionType imgRegion = flippedImageFixed->GetLargestPossibleRegion();
  205. VolumeImageType::SizeType imgSize = imgRegion.GetSize();
  206. int x = imgSize[0];
  207. int y = imgSize[1];
  208. int z = imgSize[2];
  209. OutputPixelType* imageData = outputImage->GetBufferPointer();
  210. int pixelNum = x * y * z;
  211. // ToDo 暂未将图像类型转换成不同的ColorType,只支持8位灰度图
  212. memcpy(fusedVolumeInfo.dataBuffer, imageData, pixelNum);
  213. fusedVolumeInfo.x = x;
  214. fusedVolumeInfo.y = y;
  215. fusedVolumeInfo.z = z;
  216. fusedVolumeInfo.spacing = _spacing;
  217. fusedVolumeInfo.colorType = _colorType;
  218. return true;
  219. }
  220. catch (const std::exception& ex)
  221. {
  222. ErrorMsg::SetErrorMsg(FusionError, { ex.what() });
  223. return false;
  224. }
  225. }
  226. /// <summary>
  227. /// 从输入的VolumeData里创建ITKImage
  228. /// </summary>
  229. /// <param name="volumeInfo"></param>
  230. /// <param name="image"></param>
  231. /// <param name="flip"></param>
  232. /// <returns></returns>
  233. ImportFilterType::Pointer FusionHelper::CreateITKImageFromImportVolumeDatas(UniformVolumeDataInfo volumeInfo, bool flip)
  234. {
  235. uint8_t* dataBuffer = volumeInfo.dataBuffer;
  236. int x = volumeInfo.x;
  237. int y = volumeInfo.y;
  238. int z = volumeInfo.z;
  239. // region的size
  240. SizeType size;
  241. size[0] = x;
  242. size[1] = y;
  243. size[2] = z;
  244. // region的origin
  245. IndexType start;
  246. start.Fill(0);
  247. // region
  248. RegionType region;
  249. region.SetIndex(start);
  250. region.SetSize(size);
  251. ImportFilterType::Pointer importFilter = ImportFilterType::New();
  252. importFilter->SetRegion(region);
  253. // origin
  254. OriginType origin[3] = { 0.0,0.0,0.0 };
  255. importFilter->SetOrigin(origin);
  256. // spacing
  257. SpacingType spacing[3];
  258. spacing[0] = volumeInfo.spacing;
  259. spacing[1] = volumeInfo.spacing;
  260. spacing[2] = volumeInfo.spacing;
  261. importFilter->SetSpacing(spacing);
  262. // 创建自己的Buffer(因为要把输入的volumeData里的数据转换一下数据类型)
  263. int numPixels = x * y * z;
  264. // 注意:itk采用SmartPointer(引用计数为0时,会自动调用Delete),
  265. // 如果itkOwnTheBuffer设为true,则由itk来管理所占用的空间,
  266. // 因为itkOwnTheBuffer为true,所以这里new出来的可以不用delete
  267. PixelType* localDataBuffer = new PixelType[numPixels];
  268. int bytesPerPixel = ImageHelper::GetBytesPerPixel(volumeInfo.colorType);
  269. // ToDo 这里没有根据ColorType将彩色图像转换成所需的灰度值
  270. if(flip)
  271. {
  272. for(int ni=0; ni<z*y; ni++)
  273. {
  274. for(int nx=0; nx<x;nx++)
  275. {
  276. int index = ni * x + x - nx;
  277. auto pixelVal = dataBuffer[index * bytesPerPixel];
  278. localDataBuffer[ni * x + nx] = static_cast<PixelType>(pixelVal);
  279. }
  280. }
  281. }
  282. else
  283. {
  284. for (int ni = 0; ni < numPixels; ni++)
  285. {
  286. auto pixelVal = dataBuffer[ni * bytesPerPixel];
  287. localDataBuffer[ni] = static_cast<PixelType>(pixelVal);
  288. }
  289. }
  290. importFilter->SetImportPointer(localDataBuffer, numPixels, ItkOwnTheBuffer);
  291. // 返回图像
  292. return importFilter;
  293. }
  294. /// <summary>
  295. /// 计算fixedImage和movingImage之间的平移
  296. /// 用基于互相关的测度,用梯度下降的优化器
  297. /// </summary>
  298. /// <returns></returns>
  299. Transform3DType::ParametersType FusionHelper::Get3DTransformationsWithCorrelationMetrics()
  300. {
  301. using Optimizer3DType = itk::RegularStepGradientDescentOptimizerv4<double>;
  302. using Metric3DType = itk::CorrelationImageToImageMetricv4<VolumeImageType, VolumeImageType>;
  303. using Registration3DType = itk::ImageRegistrationMethodv4<VolumeImageType, VolumeImageType, Transform3DType>;
  304. // 设置regist3d
  305. Optimizer3DType::Pointer optimizer3d = Optimizer3DType::New();
  306. Metric3DType::Pointer metric3d = Metric3DType::New();
  307. Registration3DType::Pointer regist3d = Registration3DType::New();
  308. regist3d->SetOptimizer(optimizer3d);
  309. regist3d->SetMetric(metric3d);
  310. // 设置初始值(xyz两个方向的初始值都设为0)
  311. // 读入两幅图的尺寸
  312. Transform3DType::Pointer initTrans3d = Transform3DType::New();
  313. Optimizer3DType::ParametersType initialPara3d(initTrans3d->GetNumberOfParameters());
  314. initialPara3d[0] = 0.0;
  315. initialPara3d[1] = 0.0;
  316. initialPara3d[2] = 0.0;
  317. initTrans3d->SetParameters(initialPara3d);
  318. regist3d->SetMovingInitialTransform(initTrans3d);
  319. // 设置固定图像和移动图像
  320. regist3d->SetFixedImage(_fixedImage);
  321. regist3d->SetMovingImage(_movingImage);
  322. regist3d->SetObjectName("TranslationRegistration");
  323. // 设置收缩和平滑参数
  324. constexpr unsigned int numberOfLevels = 1;
  325. Registration3DType::ShrinkFactorsArrayType shrinkFactorsPerLevel;
  326. shrinkFactorsPerLevel.SetSize(numberOfLevels);
  327. shrinkFactorsPerLevel[0] = 8;
  328. Registration3DType::SmoothingSigmasArrayType smoothingSigmasPerLevel1;
  329. smoothingSigmasPerLevel1.SetSize(numberOfLevels);
  330. smoothingSigmasPerLevel1[0] = 2.0;
  331. regist3d->SetNumberOfLevels(numberOfLevels);
  332. regist3d->SetShrinkFactorsPerLevel(shrinkFactorsPerLevel);
  333. regist3d->SetSmoothingSigmasPerLevel(smoothingSigmasPerLevel1);
  334. // 设置学习率和补偿
  335. optimizer3d->SetLearningRate(20 * _spacing);
  336. optimizer3d->SetMinimumStepLength(0.5 * _spacing);
  337. //每次导数的方向突然改变时,优化器假定已经通过了一个局部极值,
  338. //并通过一个松弛因子(Relaxation Factor)来减小步长。取值范围 0~1,默认0.5
  339. optimizer3d->SetRelaxationFactor(0.9);
  340. optimizer3d->SetNumberOfIterations(100);
  341. optimizer3d->SetReturnBestParametersAndValue(true);
  342. #if ENABLE_OBSERVE
  343. CommandIterationUpdate1::Pointer observer1 = CommandIterationUpdate1::New();
  344. optimizer3d->AddObserver(itk::IterationEvent(), observer1);
  345. using TranslationCommandType = RegistrationInterfaceCommand1<Registration3DType>;
  346. TranslationCommandType::Pointer command1 = TranslationCommandType::New();
  347. regist3d->AddObserver(itk::MultiResolutionIterationEvent(), command1);
  348. #endif
  349. regist3d->Update();
  350. #if ENABLE_OBSERVE
  351. std::cout << std::endl;
  352. std::cout
  353. << "regist3d stop condition: "
  354. << regist3d->GetOptimizer()->GetStopConditionDescription()
  355. << std::endl;
  356. #endif
  357. Transform3DType::ConstPointer transTransform = regist3d->GetTransform();
  358. Transform3DType::ParametersType parameters3d = transTransform->GetParameters();
  359. #if ENABLE_OBSERVE
  360. const unsigned int numberOfIterations1 = optimizer3d->GetCurrentIteration();
  361. const double bestValue1 = optimizer3d->GetValue();
  362. const double translation3d_x = parameters3d[0];
  363. const double translation3d_y = parameters3d[1];
  364. const double translation3d_z = parameters3d[2];
  365. std::cout << "3D transformation Result = " << std::endl;
  366. std::cout << " Iterations = " << numberOfIterations1 << std::endl;
  367. std::cout << " Metric value = " << bestValue1 << std::endl;
  368. std::cout << " Translation X = " << translation3d_x << std::endl;
  369. std::cout << " Translation Y = " << translation3d_y << std::endl;
  370. std::cout << " Translation Z = " << translation3d_z << std::endl;
  371. #endif
  372. return parameters3d;
  373. }
  374. /// <summary>
  375. /// 计算fixedImage和movingImage之间的平移
  376. /// 用基于梯度差值的测度,用梯度下降的优化器
  377. /// </summary>
  378. /// <returns></returns>
  379. Transform2DType::ParametersType FusionHelper::Get2DTransformationsWithGradientDifferenceMetric(double translationZ)
  380. {
  381. // 第一步:抽取出z方向中心位置处的一张图(取_fixedImage和_moxingImage重叠部分的中间值)
  382. int pixelTransZ = std::abs(std::ceil(translationZ / _spacing));
  383. // 先得到固定图像和移动图像的z
  384. VolumeImageType::RegionType fixedImgRegion = _fixedImage->GetLargestPossibleRegion();
  385. VolumeImageType::SizeType fixedImgSize = fixedImgRegion.GetSize();
  386. int fixedImgZ = fixedImgSize[2];
  387. VolumeImageType::RegionType movingImgRegion = _movingImage->GetLargestPossibleRegion();
  388. VolumeImageType::SizeType movingImgSize = movingImgRegion.GetSize();
  389. int movingImgZ = movingImgSize[2];
  390. // 如果固定图像和移动图像之间没有重合区域,则直接报错
  391. int minImgZ = std::min(movingImgZ, fixedImgZ);
  392. if(pixelTransZ > minImgZ)
  393. {
  394. char strMsgBuff[32];
  395. std::snprintf(strMsgBuff, 32, "translationZ ( %f ) out of range.", translationZ);
  396. throw std::invalid_argument(strMsgBuff);
  397. }
  398. int indexCenter = (pixelTransZ + minImgZ) /2;
  399. int indexInFix, indexInMoving;
  400. if(translationZ <0)
  401. {
  402. indexInFix = indexCenter;
  403. indexInMoving = indexCenter - pixelTransZ;
  404. }
  405. else
  406. {
  407. indexInFix = indexCenter + pixelTransZ;
  408. indexInMoving = indexCenter;
  409. }
  410. // 提取出固定图像和移动图像中所需的那一帧
  411. auto sliceFixed = ExtractOneFrame(_fixedImage,indexInFix);
  412. auto sliceMoving = ExtractOneFrame(_movingImage,indexInMoving);
  413. sliceFixed->Update();
  414. sliceMoving->Update();
  415. #if ENABLE_OBSERVE
  416. WriteBmpImage(sliceFixed, "sliceInCenterFixed");
  417. WriteBmpImage(sliceMoving, "sliceInCenterMoving");
  418. #endif
  419. //// 第二步:把两幅图分别flip (为了让我们感兴趣的重叠区域在图像的左侧)
  420. // 注:因为改变策略,直接让固定图像翻转了,因此这里不用再翻转
  421. //using FlipFilterType = itk::FlipImageFilter<SliceImageType>;
  422. //// 只翻转0轴,即x轴
  423. //FlipFilterType::FlipAxesArrayType flipArray;
  424. //flipArray[0] = 1;
  425. //flipArray[1] = 0;
  426. //// 翻转固定图像
  427. //FlipFilterType::Pointer filterFixed = FlipFilterType::New();
  428. //filterFixed->SetFlipAxes(flipArray);
  429. //filterFixed->SetInput(sliceFixed);
  430. //SliceImageType::Pointer flipImageFixed = filterFixed->GetOutput();
  431. //flipImageFixed->Update();
  432. //// 翻转移动图像
  433. //FlipFilterType::Pointer filterMoving = FlipFilterType::New();
  434. //filterMoving->SetFlipAxes(flipArray);
  435. //filterMoving->SetInput(sliceMoving);
  436. //SliceImageType::Pointer flipImageMoving = filterMoving->GetOutput();
  437. //flipImageMoving->Update();
  438. // 第三步:对图像做高斯平滑
  439. using GaussianFilterType = itk::DiscreteGaussianImageFilter<SliceImageType, SliceImageType>;
  440. double sigma = 0.25;
  441. double variance = sigma * sigma;
  442. // 最大的核尺寸,默认是32
  443. const unsigned int maxKernelWidth = 32;
  444. // 平滑固定图像
  445. GaussianFilterType::Pointer gaussianFilterFixed = GaussianFilterType::New();
  446. gaussianFilterFixed->SetInput(sliceFixed);
  447. gaussianFilterFixed->SetVariance(variance);
  448. gaussianFilterFixed->SetMaximumKernelWidth(maxKernelWidth);
  449. gaussianFilterFixed->Update();
  450. SliceImageType::Pointer gaussianImageFixed = gaussianFilterFixed->GetOutput();
  451. gaussianImageFixed->Update();
  452. // 平滑移动图像
  453. GaussianFilterType::Pointer gaussianFilterMoving = GaussianFilterType::New();
  454. gaussianFilterMoving->SetInput(sliceMoving);
  455. gaussianFilterMoving->SetVariance(variance);
  456. gaussianFilterMoving->SetMaximumKernelWidth(maxKernelWidth);
  457. gaussianFilterMoving->Update();
  458. SliceImageType::Pointer gaussianImageMoving = gaussianFilterMoving->GetOutput();
  459. gaussianImageMoving->Update();
  460. #if ENABLE_OBSERVE
  461. unsigned int dim = gaussianFilterFixed->GetFilterDimensionality();
  462. itk::FixedArray<double, 2> sigmaArr = gaussianFilterFixed->GetSigmaArray();
  463. double sigmaval = gaussianFilterFixed->GetSigma();
  464. itk::FixedArray<double, 2> errors = gaussianFilterFixed->GetMaximumError();
  465. int kernelWidth = gaussianFilterFixed->GetMaximumKernelWidth();
  466. std::cout << " dim = " << dim << " sigmaArr = " << sigmaArr << " sigma1 = " << sigmaval
  467. << " errors = " << errors << " kernelWidth = " << kernelWidth << std::endl;
  468. WriteBmpImage(gaussianImageFixed, "gaussianImageFixed");
  469. WriteBmpImage(gaussianImageMoving, "gaussianImageMoving");
  470. #endif
  471. // 第四步 设置ROI
  472. using RoiFilterType = itk::RegionOfInterestImageFilter<SliceImageType, SliceImageType>;
  473. float offsetX = 0.2f;
  474. int offsetXInPixel = offsetX * movingImgSize[0];
  475. // 给固定图像设置ROI
  476. RoiFilterType::Pointer roiFilterFixed = RoiFilterType::New();
  477. SliceImageType::RegionType roiFixed;
  478. SliceImageType::SizeType roiSizeFixed;
  479. roiSizeFixed[0] = fixedImgSize[0];
  480. roiSizeFixed[1] = fixedImgSize[1];
  481. roiSizeFixed[0] -= offsetXInPixel;
  482. SliceImageType::IndexType roiStartFixed;
  483. roiStartFixed.Fill(0);
  484. roiFixed.SetIndex(roiStartFixed);
  485. roiFixed.SetSize(roiSizeFixed);
  486. roiFilterFixed->SetRegionOfInterest(roiFixed);
  487. roiFilterFixed->SetInput(gaussianImageFixed);
  488. SliceImageType::Pointer roiImageFixed = roiFilterFixed->GetOutput();
  489. roiImageFixed->Update();
  490. // 给移动图像设置ROI
  491. RoiFilterType::Pointer roiFilterMoving = RoiFilterType::New();
  492. SliceImageType::RegionType roiMoving;
  493. SliceImageType::SizeType roiSizeMoving;
  494. roiSizeMoving[0] = movingImgSize[0];
  495. roiSizeMoving[1] = movingImgSize[1];
  496. roiSizeMoving[0] -= offsetXInPixel;
  497. SliceImageType::IndexType roiStartMoving;
  498. roiStartMoving.Fill(0);
  499. roiMoving.SetIndex(roiStartMoving);
  500. roiMoving.SetSize(roiSizeMoving);
  501. roiFilterMoving->SetRegionOfInterest(roiMoving);
  502. roiFilterMoving->SetInput(gaussianImageMoving);
  503. SliceImageType::Pointer roiImageMoving = roiFilterMoving->GetOutput();
  504. roiImageMoving->Update();
  505. // 第五步,将固定图像和移动图像进行2D配准
  506. using Optimizer2DType = itk::RegularStepGradientDescentOptimizer;
  507. using Registration2DType = itk::ImageRegistrationMethod<SliceImageType, SliceImageType>;
  508. using Metric2DType = itk::GradientDifferenceImageToImageMetric<SliceImageType, SliceImageType>;
  509. using InterpolatorType = itk::LinearInterpolateImageFunction<SliceImageType, double>;
  510. // 配准相关参数初始化
  511. Transform2DType::Pointer initTrans2d = Transform2DType::New();
  512. Optimizer2DType::Pointer optimizer2d = Optimizer2DType::New();
  513. InterpolatorType::Pointer interpolator2d = InterpolatorType::New();
  514. Registration2DType::Pointer regist2d = Registration2DType::New();
  515. Metric2DType::Pointer metric2d = Metric2DType::New();
  516. // 设置配准参数
  517. regist2d->SetOptimizer(optimizer2d);
  518. regist2d->SetTransform(initTrans2d);
  519. regist2d->SetInterpolator(interpolator2d);
  520. metric2d->SetDerivativeDelta(0.5);
  521. regist2d->SetMetric(metric2d);
  522. // 设置移动和固定图像
  523. regist2d->SetFixedImage(roiImageFixed);
  524. regist2d->SetMovingImage(roiImageMoving);
  525. regist2d->SetFixedImageRegion(gaussianImageFixed->GetBufferedRegion());
  526. // 设置初始值
  527. Registration2DType::ParametersType initialPara2d(initTrans2d->GetNumberOfParameters());
  528. initialPara2d[0] = 0.0;
  529. initialPara2d[1] = 0.0;
  530. regist2d->SetInitialTransformParameters(initialPara2d);
  531. // 设置步长
  532. // 根据spacing来设置步长?(Danzel原来代码里写的1mm是0.1个像素,所以步长设为了1.5, 最小步长是0.1)
  533. optimizer2d->SetMaximumStepLength(15 * _spacing);
  534. optimizer2d->SetMinimumStepLength(0.5 * _spacing);
  535. optimizer2d->SetNumberOfIterations(100);
  536. optimizer2d->SetGradientMagnitudeTolerance(1e-40);
  537. optimizer2d->MaximizeOn();
  538. #if ENABLE_OBSERVE
  539. CommandIterationUpdate2::Pointer observer = CommandIterationUpdate2::New();
  540. optimizer2d->AddObserver(itk::IterationEvent(), observer);
  541. #endif
  542. regist2d->Update();
  543. #if ENABLE_OBSERVE
  544. std::cout << "Optimizer stop condition: "
  545. << regist2d->GetOptimizer()->GetStopConditionDescription()
  546. << std::endl;
  547. #endif
  548. const Transform2DType::ParametersType parameters2d = regist2d->GetLastTransformParameters();
  549. #if ENABLE_OBSERVE
  550. const unsigned int numberOfIterations2 = optimizer2d->GetCurrentIteration();
  551. const double bestValue2 = optimizer2d->GetValue();
  552. double translation2d_x = parameters2d[0];
  553. double translation2d_y = parameters2d[1];
  554. // Print out results
  555. std::cout << "在 X-O-Y 平面的配准 Result = " << std::endl;
  556. std::cout << " Iterations = " << numberOfIterations2 << std::endl;
  557. std::cout << " Metric value = " << bestValue2 << std::endl;
  558. std::cout << " Translation X = " << translation2d_x << std::endl;
  559. std::cout << " Translation Y = " << translation2d_y << std::endl;
  560. #endif
  561. return parameters2d;
  562. }
  563. /// <summary>
  564. /// 取其中的一帧图
  565. /// </summary>
  566. /// <param name="image"></param>
  567. /// <param name="indexZ"></param>
  568. /// <returns></returns>
  569. SliceImageType::Pointer FusionHelper::ExtractOneFrame(VolumeImageType::Pointer image,int indexZ)
  570. {
  571. using FilterType = itk::ExtractImageFilter<VolumeImageType, SliceImageType>;
  572. FilterType::Pointer filter = FilterType::New();
  573. filter->SetDirectionCollapseToSubmatrix();
  574. filter->InPlaceOn();
  575. RegionType imgRegion = image->GetLargestPossibleRegion();
  576. SizeType size = imgRegion.GetSize();
  577. size[2] = 0;
  578. IndexType start;
  579. start[0] = 0;
  580. start[1] = 0;
  581. start[2] = indexZ;
  582. RegionType desiredRegion;
  583. desiredRegion.SetSize(size);
  584. desiredRegion.SetIndex(start);
  585. filter->SetExtractionRegion(desiredRegion);
  586. filter->SetInput(image);
  587. SliceImageType::Pointer sliceImage = filter->GetOutput();
  588. sliceImage->Update();
  589. return sliceImage;
  590. }
  591. /// <summary>
  592. /// 将体数据写成mha格式的文件到指定路径
  593. /// </summary>
  594. /// <param name="image"></param>
  595. /// <param name="fileName"></param>
  596. void FusionHelper::WriteMhaImage(VolumeImageType::Pointer imagePointer, std::string fileName)
  597. {
  598. // 创建目录
  599. if(access(_outputFolderName.c_str(),0) == -1)
  600. {
  601. mkdir(_outputFolderName.c_str());
  602. }
  603. std::string subFolder = _outputFolderName +"\\Mha";
  604. if (access(subFolder.c_str(), 0) == -1)
  605. {
  606. mkdir(subFolder.c_str());
  607. }
  608. using ImageIOType = itk::MetaImageIO;
  609. ImageIOType::Pointer metaIO = itk::MetaImageIO::New();
  610. using WriteImageType = itk::Image<unsigned char, 3>;
  611. using CastSliceFilterType = itk::CastImageFilter<VolumeImageType, WriteImageType>;
  612. using WriterType = itk::ImageFileWriter<WriteImageType>;
  613. WriterType::Pointer writer = WriterType::New();
  614. CastSliceFilterType::Pointer cast = CastSliceFilterType::New();
  615. cast->SetInput(imagePointer);
  616. writer->SetImageIO(metaIO);
  617. writer->SetInput(cast->GetOutput());
  618. writer->SetFileName(subFolder+"\\"+fileName+".mha");
  619. writer->Update();
  620. }
  621. /// <summary>
  622. /// 将切面图像写成bmp格式的文件到指定路径
  623. /// </summary>
  624. /// <param name="image"></param>
  625. /// <param name="fileName"></param>
  626. void FusionHelper::WriteBmpImage(SliceImageType* image, std::string fileName)
  627. {
  628. // 创建目录
  629. if (access(_outputFolderName.c_str(), 0) == -1)
  630. {
  631. mkdir(_outputFolderName.c_str());
  632. }
  633. std::string subFolder = _outputFolderName + "\\Bmp";
  634. if (access(subFolder.c_str(), 0) == -1)
  635. {
  636. mkdir(subFolder.c_str());
  637. }
  638. using WriteImageType = itk::Image<unsigned char, 2>;
  639. using CastSliceFilterType = itk::CastImageFilter<SliceImageType, WriteImageType>;
  640. using WriterType = itk::ImageFileWriter<WriteImageType>;
  641. WriterType::Pointer writer = WriterType::New();
  642. CastSliceFilterType::Pointer cast = CastSliceFilterType::New();
  643. cast->SetInput(image);
  644. writer->SetImageIO(itk::BMPImageIO::New());
  645. writer->SetInput(cast->GetOutput());
  646. writer->SetFileName(subFolder+"\\" + fileName + ".bmp");
  647. writer->Update();
  648. }
  649. /// <summary>
  650. /// 移动一幅图像
  651. /// </summary>
  652. /// <param name="translation"></param>
  653. /// <param name="image"></param>
  654. /// <returns></returns>
  655. VolumeImageType::Pointer FusionHelper::Translate3DImage(VolumeImageType::Pointer image,Transform3DType::OutputVectorType translation)
  656. {
  657. // 平移固定图像
  658. using ResampleFilterType = itk::ResampleImageFilter<VolumeImageType, VolumeImageType>;
  659. OutputImageType* outputImage = nullptr;
  660. Transform3DType::Pointer transform = Transform3DType::New();
  661. transform->SetOffset(translation);
  662. ResampleFilterType::Pointer resampler = ResampleFilterType::New();
  663. resampler->SetTransform(transform);
  664. resampler->SetInput(image);
  665. VolumeImageType::RegionType imgRegion = image->GetLargestPossibleRegion();
  666. VolumeImageType::SizeType imgSize = imgRegion.GetSize();
  667. resampler->SetSize(imgSize);
  668. auto origin = image->GetOrigin();
  669. resampler->SetOutputOrigin(origin);
  670. auto spacing = image->GetSpacing();
  671. resampler->SetOutputSpacing(spacing);
  672. auto direction = image->GetDirection();
  673. resampler->SetOutputDirection(direction);
  674. resampler->SetDefaultPixelValue(0);
  675. auto resultImage = resampler->GetOutput();
  676. resultImage->Update();
  677. return resultImage;
  678. }
  679. /// <summary>
  680. /// 沿着x轴方向将两个Itk三维图像直接拼到一起
  681. /// </summary>
  682. /// <param name="oneImage"></param>
  683. /// <param name="anotherImage"></param>
  684. /// <returns></returns>
  685. ImportFilterType::Pointer FusionHelper::ConcatTwoVolumeImageAlongX(VolumeImageType::Pointer oneImage, bool flipOneImage,
  686. VolumeImageType::Pointer anotherImage, bool flipAnotherImage)
  687. {
  688. // 获取两幅图像的数据指针
  689. PixelType* imageDataOne = oneImage->GetBufferPointer();
  690. PixelType* imageDataAnother = anotherImage->GetBufferPointer();
  691. // 获取两幅图像的尺寸信息
  692. VolumeImageType::RegionType imgRegionOne = oneImage->GetLargestPossibleRegion();
  693. VolumeImageType::SizeType imgSizeOne = imgRegionOne.GetSize();
  694. int sizeXOne = imgSizeOne[0];
  695. int sizeYOne = imgSizeOne[1];
  696. int sizeZOne = imgSizeOne[2];
  697. VolumeImageType::RegionType imgRegionAnother = anotherImage->GetLargestPossibleRegion();
  698. VolumeImageType::SizeType imgSizeAnother = imgRegionAnother.GetSize();
  699. int sizeXAnother = imgSizeAnother[0];
  700. int sizeYAnother = imgSizeAnother[1];
  701. int sizeZAnother = imgSizeAnother[2];
  702. // 确定输出图像的尺寸
  703. int sizeXConcat = sizeXOne + sizeXAnother;
  704. int sizeYConcat = std::max(sizeYOne, sizeYAnother);
  705. int sizeZConcat = std::max(sizeZOne, sizeZAnother);
  706. // 创建所需空间
  707. int numPixels = sizeXConcat * sizeYConcat * sizeZConcat;
  708. PixelType* imageDataConcat = new PixelType[numPixels];
  709. memset(imageDataConcat, 0, sizeof(PixelType) * numPixels);
  710. int indexDst = 0;
  711. int indexSrc = 0;
  712. // 拷贝第一幅图到指定位置(只能拷在图像范围内的像素值)
  713. for (int nz = 0; nz < sizeZOne; nz++)
  714. {
  715. for (int ny = 0; ny < sizeYOne; ny++)
  716. {
  717. for (int nx = 0; nx < sizeXOne; nx++)
  718. {
  719. if(flipOneImage)
  720. {
  721. indexSrc = nz * sizeYOne * sizeXOne + ny * sizeXOne + sizeXOne - nx;
  722. }
  723. else
  724. {
  725. indexSrc = nz * sizeYOne * sizeXOne + ny * sizeXOne + nx;
  726. }
  727. indexDst = nz * sizeYConcat * sizeXConcat + ny * sizeXConcat + nx;
  728. imageDataConcat[indexDst] = imageDataOne[indexSrc];
  729. }
  730. }
  731. }
  732. // 拷贝第二幅图到指定位置(只能拷在图像范围内的像素值)将其直接贴在第一幅图的右侧
  733. for (int nz = 0; nz < sizeZAnother; nz++)
  734. {
  735. for (int ny = 0; ny < sizeYAnother; ny++)
  736. {
  737. for (int nx = 0; nx < sizeXAnother; nx++)
  738. {
  739. if(flipAnotherImage)
  740. {
  741. indexSrc = nz * sizeYAnother * sizeXAnother + ny * sizeXAnother + sizeXAnother - nx;
  742. }
  743. else
  744. {
  745. indexSrc = nz * sizeYAnother * sizeXAnother + ny * sizeXAnother + nx;
  746. }
  747. indexDst = nz * sizeYConcat * sizeXConcat + ny * sizeXConcat + nx + sizeXOne;
  748. imageDataConcat[indexDst] = imageDataAnother[indexSrc];
  749. }
  750. }
  751. }
  752. // 从已知dataBuffer里创建图像
  753. ImportFilterType::Pointer importFilter = ImportFilterType::New();
  754. // region的size
  755. SizeType size;
  756. size[0] = sizeXConcat;
  757. size[1] = sizeYConcat;
  758. size[2] = sizeZConcat;
  759. // region的origin
  760. IndexType start;
  761. start.Fill(0);
  762. // region
  763. RegionType region;
  764. region.SetIndex(start);
  765. region.SetSize(size);
  766. importFilter->SetRegion(region);
  767. // origin
  768. OriginType origin[3] = { 0.0,0.0,0.0 };
  769. importFilter->SetOrigin(origin);
  770. // spacing
  771. SpacingType spacing[3];
  772. spacing[0] = _spacing;
  773. spacing[1] = _spacing;
  774. spacing[2] = _spacing;
  775. importFilter->SetSpacing(spacing);
  776. // 图像区指针
  777. importFilter->SetImportPointer(imageDataConcat, numPixels, ItkOwnTheBuffer);
  778. // 返回图像
  779. return importFilter;
  780. }