基于dcmtk的dicom工具 第十一章 加载dicom文件多帧图数据

文章目录


前言

继前一章,本章实现多帧图显示,增加"上一帧"、"下一帧"两个按钮,和鼠标滚动消息滚动不同帧。

效果如下:


一、加载多帧图的方法

1. DicomImage 构造函数

下面是dcmtk DicomImage类的一个构造函数,创建dcmtk DicomImage对象时,单帧图只需要填写前两个参数即可,多帧图flags参数填 CIF_UsePartialAccessToPixelData,再配合 fstart, fcount参数即可加载第fstart帧到 fstart+fcount帧的图像数据

cpp 复制代码
 /** constructor, use a given DcmObject
     *
     ** @param  object  pointer to DICOM data structures (fileformat, dataset or item).
     *                  (do not delete while referenced, i.e. while this image object or any
     *                   descendant exists; not deleted within dcmimage unless configuration flag
     *                   CIF_TakeOverExternalDataset is set - in this case do not delete it at all)
     *  @param  xfer    transfer syntax of the 'object'.
     *                  (could also be EXS_Unknown in case of fileformat or dataset)
     *  @param  flags   configuration flags (CIF_xxx, see diutils.h)
     *  @param  fstart  first frame to be processed (optional, 0 = 1st frame), all subsequent use
     *                  of parameters labeled 'frame' in this class refers to this start frame.
     *  @param  fcount  number of frames (optional, 0 = all frames)
     */
    DicomImage(DcmObject *object,
               const E_TransferSyntax xfer,
               const unsigned long flags = 0,
               const unsigned long fstart = 0,
               const unsigned long fcount = 0);

2. DcmParser Open函数

DcmParser类中的Open, CreateDIB两个函数中处理多帧图的代码

Open函数中的代码,默认多帧图fcount=5,即只加载最多5帧数据到内存,可根据电脑内存修改:

cpp 复制代码
BOOL DcmParser::Open(std::string dcmfile)
{
	if (m_bParserValid)
		return TRUE;

	dcmAcceptUnexpectedImplicitEncoding.set(OFFalse);
	dcmPreferVRFromDataDictionary.set(OFFalse);

	OFCondition cond;
	cond = m_dcmFile.loadFile(dcmfile.c_str());
   ...
  
	if (m_nFrameCount == 1) 
	{
		m_pDcmImg = new DicomImage(pDataset, m_newXfer);
	}
	else 
	{   // 多帧图,只加载最多5帧数据
		m_nLoadCount = m_nFrameCount > 5 ? 5 : m_nFrameCount;
		m_nFrameStart = 0;
		unsigned long flag = CIF_UsePartialAccessToPixelData;
		m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);
	}
   ...

	return m_bParserValid;
}

3. DcmParser CreateDIB函数

对于多帧图,Open函数中默认加载的是前五帧的图像数据,索引[0,4],当CreateDIB函数的frame参数值超过4时,需要加载下一个五帧的图像数据,索引[5,9]

最重要的四行代码:

  1. int fend = m_nFrameStart + m_nLoadCount - 1; 用来判断frame是否超出范围
  2. m_nFrameStart = frame / m_nLoadCount * m_nLoadCount; 计算新的起始帧索引
  3. m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount); 加载新五帧数据
  4. size = di->createWindowsDIB(pdib, 0, frame-m_nFrameStart, 24, 1); 创建当前frame帧位图数据
cpp 复制代码
BOOL DcmParser::CreateDIB(void*& pdib, int& w, int& h, double wc, double ww, int frame/* = 0*/, bool bneg/* = false*/)
{
	DcmDataset* pDataset = m_dcmFile.getDataset();

	int fend = m_nFrameStart + m_nLoadCount - 1;
	if (!(frame >= m_nFrameStart && frame <= fend)) 
	{
		m_nFrameStart = frame / m_nLoadCount * m_nLoadCount;
		delete m_pDcmImg;

		unsigned long flag = CIF_UsePartialAccessToPixelData;
		m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);
	}
	
    ...

  size = di->createWindowsDIB(pdib, 0, frame-m_nFrameStart, 24, 1);
   ...
  
}

4. 为Displayer类添加PrevFrame,NextFrame两个函数

在绘制参数DrawParam.nFrame中记录当前帧索引,只需要增减nFrame,再调用DcmParser中的CreateDIB重建生成位图数据即可。

cpp 复制代码
void Displayer::PrevFrame()
{
	int nFrame = m_drawParam.nFrame - 1;
	if (nFrame < 0) {
		return;
	}

	m_drawParam.nFrame = nFrame;

	if (m_pDib)
	{
		delete[] m_pDib;
		m_pDib = nullptr;
	}

	m_pImage->CreateDIB(m_pDib, m_drawParam.width, m_drawParam.height, m_drawParam.winCenter,
		m_drawParam.winWidth, m_drawParam.nFrame, m_drawParam.bNagtive);

	FitToView();
}

void Displayer::NextFrame()
{
	int nFrameCount = m_pImage->GetFrameCount();

	int nFrame = m_drawParam.nFrame + 1;
	if (nFrame > nFrameCount - 1) {
		return;
	}

	m_drawParam.nFrame = nFrame;

	if (m_pDib)
	{
		delete[] m_pDib;
		m_pDib = nullptr;
	}

	m_pImage->CreateDIB(m_pDib, m_drawParam.width, m_drawParam.height, m_drawParam.winCenter,
		m_drawParam.winWidth, m_drawParam.nFrame, m_drawParam.bNagtive);

	FitToView();
}

二、按钮滚动帧

  1. 在对话框中添加"上一帧","下一帧"两个按钮
  2. 在CDcmImageDlg中为按钮添加响应事件
  3. 图像加载时检查dicom文件是否为多帧图,控制两个按钮是否变灰
cpp 复制代码
void CDcmImageDlg::OnBnClickedButtonPrevFrame()
{
	m_disp.PrevFrame();
}


void CDcmImageDlg::OnBnClickedButtonNextFrame()
{
	m_disp.NextFrame();
}

void CDcmImageDlg::OnSelchangeListDcm()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	int idx = m_list.GetCurSel();
	m_list.GetText(idx, str);
	m_disp.LoadFile(str);

	int nFrames = m_disp.GetFrameCount();
	if (nFrames > 1) {
		GetDlgItem(IDC_BUTTON_PREV_FRAME)->EnableWindow(TRUE);
		GetDlgItem(IDC_BUTTON_NEXT_FRAME)->EnableWindow(TRUE);
	}
	else {
		GetDlgItem(IDC_BUTTON_PREV_FRAME)->EnableWindow(FALSE);
		GetDlgItem(IDC_BUTTON_NEXT_FRAME)->EnableWindow(FALSE);
	}

}

三、鼠标滚轮滚动帧

Displayer类中添加鼠标滚轮消息WM_MOUSEWHEEL,调用NextFrame,PrevFrame滚动帧

cpp 复制代码
BEGIN_MESSAGE_MAP(Displayer, CWnd)
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_MOUSEWHEEL()   // 鼠标滚轮
END_MESSAGE_MAP()

BOOL Displayer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if (!m_pImage) return true;
	int nFrames = m_pImage->GetFrameCount();
	if (nFrames > 1) {
		if (zDelta < 0) {
			NextFrame();
		}
		else {
			PrevFrame();
		}
	}

	return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}
相关推荐
sulikey3 小时前
Qt 入门简洁笔记:常用控件
c++·qt·控件·qwidget·qlabel·qpushbutton·qlineedit
一抹轻笑动人3 小时前
cpp language 语法
开发语言·c++
煤球王子3 小时前
学而时习之:C++语言基础了解
c++
承渊政道3 小时前
算法复杂度
c语言·数据结构·c++·算法·visual studio
千年奇葩4 小时前
Unity性能优化之:利用CUDA加速Unity实现大规模并行计算。从环境搭建到实战案例
c++·人工智能·unity·游戏引擎·cuda
AA陈超4 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P06-25 属性信息数据资产
c++·游戏·ue5·游戏引擎·虚幻
_dindong4 小时前
Linux网络编程:进程间关系和守护进程
linux·运维·服务器·网络·c++·学习
zhilin_tang4 小时前
如何写一个WebRTC ACE音频应用处理模块
linux·c语言·c++
Skrrapper4 小时前
【C++】C++11都有什么新特性?
数据结构·c++