与医学图像OpenGL显示相关源码解析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/rabbitbride/article/details/84287841

2018-11-20 10:40:48 这是一份20XX年的代码,大部分来自于itksnap。重新拿出来,一天的时间,深度解析,希望对这段时间有一个交待。
这里只有思路,没有多少能直接用的代码,相信对于真正需要的人能懂它的价值。如果想读源码,请转到开源的itksnap。
2018-11-20 14:56:29 可能会围绕这个软件写一系列的文章,希望能有头有尾。

技术方案:C++,opengl,cmake,itk,vtk,MFC(事实上是MFC和win32的混合编程)
编程语言用的是C++,没什么好说的,中间用到了面向对象、STL、泛型编程,大坑。这里的重头戏应该是在opengl,是本文中想说清楚的部分。至于MFC,新生代员工已经没听过这是什么东西了。cmake,itk,vtk,都属于高级工具流,唯手熟尔~


软件架构:实现了界面、逻辑、数据三家分离,每个功能模块有专门的类来管理UI,从MFC换到Qt,分分钟。


凡事总要有个开始,那就从VectorTypes这个类开始吧~

VectorTypes中引用了C++矩阵运算库VXL(类似eigen),代码中的iris_vector_fixed类继承与vxl中的vnl_vector_fixed。随后是2d与3d的宏定义:

// Common 2D vector types
typedef iris_vector_fixed<int,2> Vector2i;
typedef iris_vector_fixed<unsigned int,2> Vector2ui;
typedef iris_vector_fixed<long,2> Vector2l;
typedef iris_vector_fixed<unsigned long,2> Vector2ul;
typedef iris_vector_fixed<float,2> Vector2f;
typedef iris_vector_fixed<double,2> Vector2d;
// Common 3D vector types
typedef iris_vector_fixed<int,3> Vector3i;
typedef iris_vector_fixed<unsigned int,3> Vector3ui;
typedef iris_vector_fixed<long,3> Vector3l;
typedef iris_vector_fixed<unsigned long,3> Vector3ul;
typedef iris_vector_fixed<float,3> Vector3f;
typedef iris_vector_fixed<double,3> Vector3d;

这么做的意义在于最大程度的将图像矩阵运算的细节封装起来,使用成熟的矩阵计算方案,代码量少,好实现,可操控。


接下来要读的是OpenGLSliceTexture类。在传统魔法时代的opengl(以后统称gl)中,纹理映射是个不可小觑的概念,几乎所有与图相关的内容都是纹理。这个类就是一个从图像到纹理的转换器。

//从itkimage到gl的fliter
#include "itkImage.h"
#include "vectortypes.h"
#include <gl\gl.h> 
#include <gl\glu.h>

类的最开始就定义了私有的图像类型和图像指针,格式按照itk中的泛型:

	typedef itk::ImageBase<2> ImageBaseType;//定义基础itk格式的二维图像
	typedef itk::SmartPointer<ImageBaseType> ImageBasePointer;//定义该图像格式的图像指针
//下面这两个函数几乎获得了图像的所有信息
	template<class TPixel> void SetImage(itk::Image<TPixel,2> *inImage)
	{
		m_Image = inImage;
		m_Image->GetSource()->UpdateLargestPossibleRegion();
		m_Buffer = inImage->GetBufferPointer();
		itk::Size<2> szImage = m_Image->GetLargestPossibleRegion().GetSize();
		itk::Vector<float,2> spaceImage=m_Image->GetSpacing();
		m_SliceSize=Vector2i(szImage[0],szImage[1]);
		m_SliceSpacing=Vector2f(spaceImage[0],spaceImage[1]);
	}
	ImageBasePointer GetImage()
	{
		return m_Image;
	}

调用gl函数将像素转换成纹理,亮点在最后的glTexSubImage2D:

void OpenGLSliceTexture::Update()
{
  // Better have an image
	if(m_Image.IsNull())
		return;
  // Update the image (necessary?)
  if(m_Image->GetSource())
    m_Image->GetSource()->UpdateLargestPossibleRegion();

  // Promote the image dimensions to powers of 2
  itk::Size<2> szImage = m_Image->GetLargestPossibleRegion().GetSize();
  m_TextureSize = Vector2ui(1);

  // Use shift to quickly double the coordinates
  for (unsigned int i=0;i<2;i++)
    while (m_TextureSize(i) < szImage[i])
      m_TextureSize(i) <<= 1;

  // Create the texture index if necessary
  if(!m_IsTextureInitalized)
    {
    // Generate one texture
    glGenTextures(1,&m_TextureIndex);
    m_IsTextureInitalized = true;
    }

  // Select the texture for pixel pumping
  glBindTexture(GL_TEXTURE_2D,m_TextureIndex);

  // Properties for the texture
  glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_InterpolationMode );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_InterpolationMode );

  // Turn off modulo-4 rounding in GL
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glPixelStorei(GL_PACK_ALIGNMENT, 1);

  // Allocate texture of slightly bigger size
  glTexImage2D(GL_TEXTURE_2D, 0, m_GlComponents,m_TextureSize(0), m_TextureSize(1),
    0, m_GlFormat, m_GlType, NULL);

  // Copy a subtexture of correct size into the image 
  //关键在这里的动态纹理替换,比glTexImage2D或者gluBuild2DMipmaps高效
  //void *m_Buffer= inImage->GetBufferPointer();
  //m_Buffer就是图像的指针
  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, szImage[0], szImage[1],
    m_GlFormat, m_GlType, m_Buffer);
}

接下来是图像窗口实现类ImageView,该类的作用是将gl与win32视图绑定。该类将OpenGLSliceTexture *m_MainTexture当作成员变量传进去。
关键词:wglCreateContext

void ImageView::DrawImage()
{
	wglMakeCurrent(m_hDC,m_hGLContext);
	glViewport(0,0,m_rect.right,m_rect.bottom);              // Reset The Current Viewport
	glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
	glLoadIdentity();                                    // Reset The Projection Matrix
	gluOrtho2D(0,m_rect.right,0,m_rect.bottom);
	glMatrixMode(GL_MODELVIEW);                            // Select The Modelview Matrix
	glLoadIdentity();                                   // Reset The Modelview Matrix 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  

	// Set the color to the background color
	// glColor3dv(clrBackground.data_block());
	typedef itk::ImageBase<2> ImageBase;
	typedef itk::SmartPointer<ImageBase> ImageBasePointer;
	ImageBasePointer m_Image=m_Volume->GetDisplaySlice(m_wid);

	if(!m_Image.IsNull())
	{
		// Compute the position of the cross-hairs in display space
		Vector3ui cursorImageSpace = m_Volume->GetCursorPosition();
		m_CursorPos=cursorImageSpace;
		Vector3f cursorDisplaySpace = m_ImageToDisplayTransform.TransformPoint(to_float(cursorImageSpace) + Vector3f(0.5f));

		// Get the current slice number
		m_ImageSliceIndex = cursorImageSpace[m_ImageAxes[2]];
		m_DisplayAxisPosition = cursorDisplaySpace[2];

		glPushMatrix();
		drawMainTexture();//该函数中调用了OpenGLSliceTexture的draw函数
		glPopMatrix();
	}  
	glFlush();
	SwapBuffers(m_hDC);
	wglMakeCurrent(NULL,NULL);
}

接着读类BloodView,该类继承于MFC的窗口类CScrollView,开始从win32切换到MFC(好像有点复杂了,从itk到gl到win32再到mfc绕了一圈,但这种层层绑定的设计来源于作者的跨UI甚至跨平台,一旦有需求,将会以最小的代价换掉上层的GUI层,而下层则几乎不变)。
在该类中,将ImageView* m_ImageView;当作成员变量传了进去。

//事实上,顶层设计中,除了传传参数,并没有什么实际的内容。
void CBloodView::setSliceIndex(int newindex)
{
	if(m_ImageView)
	{
		m_ImageView->SetSliceIndex(newindex);
		m_ImageView->Update();	
	}
}

读到这里,你是不是以为这样结束了,nonono,还有分窗显示呢,于是设计了3个SliceViewXY /SliceViewXZ /SliceViewYZ 继承于BloodView。
这些类中的实现都是细节上个性化的控制了。包括与控件的交互响应。

	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnSliceFirst();
	afx_msg void OnSliceLast();
	afx_msg void OnSliceNext();
	afx_msg void OnSlicePrev();
	afx_msg void OnDestroy();
	afx_msg void OnPaint();
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);

参考文献:
http://www.itksnap.org/pmwiki/pmwiki.php
https://blog.csdn.net/darren817/article/details/79816374
http://vxl.sourceforge.net/
http://vxl.sourceforge.net/#download
https://bitbucket.org/
https://bitbucket.org/libgd/gd-libgd/downloads/
https://blog.csdn.net/zhoubl668/article/details/4563634
http://blog.sina.com.cn/s/blog_eb4d725a0102vwkf.html

展开阅读全文

没有更多推荐了,返回首页