基于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);
}
相关推荐
Ayu阿予3 分钟前
C++从源文件到可执行文件的过程
开发语言·c++
福尔摩斯张5 分钟前
基于C++的UDP网络通信系统设计与实现
linux·c语言·开发语言·网络·c++·tcp/ip·udp
hkNaruto10 分钟前
【规范】Linux平台C/C++程序版本发布调试规范手册 兼容银河麒麟
linux·c语言·c++
老王熬夜敲代码1 小时前
C++中的mutex、condition_val
c++·笔记·面试
闻缺陷则喜何志丹1 小时前
【计算几何 二分查找】P12261 [蓝桥杯 2024 国 Java B] 激光炮|普及+
c++·数学·蓝桥杯·计算几何·洛谷
Ivy_belief2 小时前
C++新特性汇总:涵盖C++11到C++23
java·c++·c++11·c++23
koddnty2 小时前
在c++中使用HOOK修改sleep函数
linux·c++
誰能久伴不乏2 小时前
深入理解 `poll` 函数:详细解析与实际应用
linux·服务器·c语言·c++·unix
仰泳的熊猫3 小时前
1140 Look-and-say Sequence
数据结构·c++·算法·pat考试
Hard but lovely3 小时前
C/C++ ---条件编译#ifdef
c语言·开发语言·c++