以下链接是本系列文章,不足之处,可在评论区讨论:
系列文章
以下链接中的代码是完整的且可运行的,链接如下,可按需下载:
dicom成像程序
本篇文章对应 专栏 从零讲解DICOM协议-成像协议中的文章DICOM成像协议剖析和DICOM成像协议实现思路,建议先看以上两篇文章以了解DICOM底层协议,有助于理解代码实现。
上篇文章DICOM成像协议编码实现-文件头解析讲解了DICOM解析引擎的实现思路和整体代码框架,并完成
本篇文章将继续进行以下几部分的代码思路讲解和实现:
依赖:
class DcmRead : public FileRead
{
public:
DcmRead(bool iscompress = false);
DcmRead(string path, bool iscompress = false);
DcmRead(char *buffer, int len, bool iscompress = false);
~DcmRead();
public:
virtual bool ReadFile(string path) override;
vector<ElementData> ParseFile(TagName tagname);
ElementFormat GetTransferSyntax();
//获得除了pixeldata以外的元素
ElementValue GetElement(TagName tagname);
ElementData GetPixelData();
DcmToBmpTag GetBMPInfo();
void SaveBMP(string path);
virtual void Clear() override;
bool IsCompress();
public:
DcmTags * GetDcmTags();
DcmFile *GetDcmFile();
void SetClassify(DcmFilePeriod *dcmclassify);
private:
//dicom文件前128个0和4个"DICM"
virtual void GetHead() override;
//dicom文件0x0002组,元数据组
virtual void GetMetadata() override;
//dicom文件信息组
virtual void GetInfodata(void *dcmelementset) override;
//dicom所有元素
//virtual void GetElements(void *dcmelementset) override;
private:
//压缩图像数据
ElementData UnCompressPixel(vector<ElementData> pixelset);
vector<ElementData> GetUnCompressPixel();
void TransferUnCompress();
private:
void MemCopy(int len, int &datalen, char **data);
void TagClassify(int startindex,vector<DcmElement> *dcmelementset);
//判断是否是元数据组0x0002(显式小端)
bool IsMetaData(char *data);
//判断是否是SQ
bool IsSQ(char * data);
int GetDataLen(char *data);
//是否完整获取SQ序列
bool IsSQEnd();
//函数是否递归操作
bool IsEmbed();
void SetVrDataLen(char *data);
void DeletePartPointer(DcmElement dcmelement);
void DeletePointer(DcmElement dcmelement);
void Init(bool iscompress);
private:
void SetBinding();
bool(DcmRead::*CompareModel)(char *source, char *des);
int(DcmRead::*ElementIntValue)(char *buffer, int len);
bool BigCompareModel(char *source, char *des);
bool LittleCompareModel(char *source, char *des);
int BigElementLen(char *buffer, int len);
int LittleElementLen(char *buffer, int len);
private:
void GetDcmClassifyInfo();
void FileClassify();
private:
bool status;
bool isbigmodel;
bool isin;
bool iscompress;
DcmFile dcmFile;
int offset;
//SQ序列嵌套层数
int embednum;
int vrlen;
int datalen;
ElementFormat ef;
CompressFormat cf;
ElementValueType et;
vector<DcmElement> sqelementset;
//Pipe *jpeg2k;
SocketClient *sc;
private:
DcmTags *dcmtags;
DcmFilePeriod *dcmclassify;
};
前面文章讲过元数据组的特点是:
元数据组是小端显式格式
元数据组和数据组都是由DICOM协议中的DataElement结构组成
DataElement由Tag,VR,Length,Value组成
Tag由group和elment组成,VR有显式和隐式之分,Length所在字节由显隐式和VR类型决定,Value长度是偶数等
元数据组中最重要的元素是传输语法(0002,0010) Transfer Syntax UID
根据以上特点,从DcmRead类中实现GetMetadata()函数
void DcmRead::GetMetadata()
{
//元数据都是显式小端
ef = ElementFormat::ExplicitLittle;
int startindex = sizeof(dcmFile.Head) + sizeof(dcmFile.DcmFlag);
TagClassify(startindex, &dcmFile.DcmElementSet);
GetTransferSyntax();
}
startindex为从元数据组在buffer内存指针的偏移地址
TagClassify函数的主要内容为:
for循环内判断是否是元数据组0002,循环执行
memcpy(dcmelement.tag, buffer + offset, sizeof(dcmelement.tag));
SetVrDataLen(dcmelement.tag);
MemCopy(vrlen, dcmelement.vr.len, &dcmelement.vr.data);
MemCopy(datalen, dcmelement.datalen.len, &dcmelement.datalen.data);
int len = (this->*ElementIntValue)(dcmelement.datalen.data, dcmelement.datalen.len);
MemCopy(len, dcmelement.data.len, &dcmelement.data.data);
显式,VR长度根据27种类型判断
class ElementBytes
{
public:
static const int ImplicitLenBytes = 4;//隐式VR,vr长度为0字节,len长度为4字节
static const int ExplicitLenComBytes = 2;//显式VR,普通类型vr长度为2字节,len长度为2字节
static const int ExplicitVrComBytes = 2;//显式VR,OB,OW,OF,SQ,UT,UN的vr长度4字节,len长度为4字节
static const int ExplicitLenOtherBytes = 4;//显式VR,OB,OW,OF,SQ,UT,UN的vr长度4字节,len长度为4字节
static const int ExplicitVrOtherBytes = 4;//显式VR,OB,OW,OF,SQ,UT,UN的vr长度4字节,len长度为4字节
};
void DcmRead::SetVrDataLen(char *data)
{
if (!IsSQ(data))
{
switch (ef)
{
case ElementFormat::ImplicitLittle:
vrlen = 0;
datalen = ElementBytes::ImplicitLenBytes;
break;
case ElementFormat::ExplicitLittle:
case ElementFormat::ExplicitBig:
case ElementFormat::CompressPixel:
char vrtype[2];
memcpy(vrtype, buffer + offset, sizeof(vrtype));
for (int j = 0; j < TagType::VrType.size(); j++)
{
if (memcmp(vrtype, TagType::VrType[j], sizeof(vrtype)) == 0)
{
vrlen = ElementBytes::ExplicitVrOtherBytes;
datalen = ElementBytes::ExplicitLenOtherBytes;
break;
}
if (j == TagType::VrType.size() - 1)
{
vrlen = ElementBytes::ExplicitVrComBytes;
datalen = ElementBytes::ExplicitLenComBytes;
}
}
break;
default:
break;
}
}
}
得到VR的长度和Length的长度后,从buffer内存指针中获取对应的数据,偏移地址累加
void DcmRead::MemCopy(int len, int &datalen, char **data)
{
datalen = len;
*data = new char[datalen];
memcpy(*data, buffer + offset, datalen);
offset += datalen;
}
小端,ElementIntValue绑定小端转换函数
void DcmRead::SetBinding()
{
if (ef == ElementFormat::ExplicitLittle || ef == ElementFormat::ImplicitLittle || ef == ElementFormat::CompressPixel)
{
CompareModel = &DcmRead::LittleCompareModel;
ElementIntValue = &DcmRead::LittleElementLen;
}
else if (ef == ElementFormat::ExplicitBig)
{
CompareModel = &DcmRead::BigCompareModel;
ElementIntValue = &DcmRead::BigElementLen;
}
}
int DcmRead::LittleElementLen(char *buffer, int len)
{
int data = 0;
int flag = 0x000000FF;
for (int i = 0; i < len; i++)
{
data |= ((buffer[i] << i * 8) & (flag << i * 8));
}
return data;
}
传输语法获取
ElementFormat DcmRead::GetTransferSyntax()
{
for (int i = 0; i < dcmFile.DcmElementSet.size(); i++)
{
if (memcmp(dcmFile.DcmElementSet[i].tag, TagType::TransferSyntaxUID, sizeof(dcmFile.DcmElementSet[i].tag)) == 0)
{
if (memcmp(dcmFile.DcmElementSet[i].data.data, TransferSyntaxType::ImplicitLittle.c_str(), dcmFile.DcmElementSet[i].data.len) == 0)
{
ef = ElementFormat::ImplicitLittle;
isbigmodel = false;
}
else if (memcmp(dcmFile.DcmElementSet[i].data.data, TransferSyntaxType::ExplicitLittle.c_str(), dcmFile.DcmElementSet[i].data.len) == 0)
{
ef = ElementFormat::ExplicitLittle;
isbigmodel = false;
}
else if (memcmp(dcmFile.DcmElementSet[i].data.data, TransferSyntaxType::ExplicitBig.c_str(), dcmFile.DcmElementSet[i].data.len) == 0)
{
ef = ElementFormat::ExplicitBig;
isbigmodel = true;
}
else //压缩格式都是显式小端
{
isbigmodel = false;
ef = ElementFormat::CompressPixel;
if (memcmp(dcmFile.DcmElementSet[i].data.data, TransferSyntaxType::Jpeg2000.c_str(), dcmFile.DcmElementSet[i].data.len) == 0)
{
cf = CompressFormat::Jpeg2000;
}
else if (memcmp(dcmFile.DcmElementSet[i].data.data, TransferSyntaxType::Jpeglossless.c_str(), dcmFile.DcmElementSet[i].data.len) == 0)
{
cf = CompressFormat::Jpeglossless;
}
}
break;
}
}
return ef;
}
至此,元数据组解析框架已基本完成,为突出重点和整体处理流程,部分函数没有列出完整内容。
下篇文章将进行DICOM数据组解析框架讲解。