文章目录
- 前言
- 一、加载多帧图的方法
-
- [1. DicomImage 构造函数](#1. DicomImage 构造函数)
- [2. DcmParser Open函数](#2. DcmParser Open函数)
- [3. DcmParser CreateDIB函数](#3. DcmParser CreateDIB函数)
- [4. 为Displayer类添加PrevFrame,NextFrame两个函数](#4. 为Displayer类添加PrevFrame,NextFrame两个函数)
- 二、按钮滚动帧
- 三、鼠标滚轮滚动帧
前言
继前一章,本章实现多帧图显示,增加"上一帧"、"下一帧"两个按钮,和鼠标滚动消息滚动不同帧。
效果如下:

一、加载多帧图的方法
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]
最重要的四行代码:
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帧位图数据
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();
}
二、按钮滚动帧
- 在对话框中添加"上一帧","下一帧"两个按钮
- 在CDcmImageDlg中为按钮添加响应事件
- 图像加载时检查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);
}