基于dcmtk的dicom工具 第十一章 加载dicom文件多帧图数据
文章目录
- 前言
- 一、加载多帧图的方法
- 1. DicomImage 构造函数
- 2. DcmParser Open函数
- 3. DcmParser CreateDIB函数
- 4. 为Displayer类添加PrevFrame,NextFrame两个函数
- 二、按钮滚动帧
- 三、鼠标滚轮滚动帧
前言
继前一章,本章实现多帧图显示,增加“上一帧”、“下一帧”两个按钮,和鼠标滚动消息滚动不同帧。
效果如下:

一、加载多帧图的方法
1. DicomImage 构造函数
下面是dcmtk DicomImage类的一个构造函数,创建dcmtk DicomImage对象时,单帧图只需要填写前两个参数即可,多帧图flags参数填 CIF_UsePartialAccessToPixelData,再配合 fstart, fcount参数即可加载第fstart帧到 fstart+fcount帧的图像数据
/** 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帧数据到内存,可根据电脑内存修改:
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]
最重要的四行代码:
int fend = m_nFrameStart + m_nLoadCount - 1;用来判断frame是否超出范围m_nFrameStart = frame / m_nLoadCount * m_nLoadCount;计算新的起始帧索引m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);加载新五帧数据size = di->createWindowsDIB(pdib, 0, frame-m_nFrameStart, 24, 1);创建当前frame帧位图数据
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重建生成位图数据即可。
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();
}
二、按钮滚动帧
- 在对话框中添加“上一帧”,“下一帧”两个按钮
- 在CDcmImageDlg中为按钮添加响应事件
- 图像加载时检查dicom文件是否为多帧图,控制两个按钮是否变灰
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滚动帧
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);
}