4 DICOM成像协议编码实现-元数据组解析

2024-04-04 10:32

本文主要是介绍4 DICOM成像协议编码实现-元数据组解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以下链接是本系列文章,不足之处,可在评论区讨论:
系列文章

以下链接中的代码是完整的且可运行的,链接如下,可按需下载:
dicom成像程序

  本篇文章对应 专栏 从零讲解DICOM协议-成像协议中的文章DICOM成像协议剖析和DICOM成像协议实现思路,建议先看以上两篇文章以了解DICOM底层协议,有助于理解代码实现。

上篇文章DICOM成像协议编码实现-文件头解析讲解了DICOM解析引擎的实现思路和整体代码框架,并完成

  1. 读取DICOM文件至内存中
  2. 读取文件头到文件头对象中

本篇文章将继续进行以下几部分的代码思路讲解和实现:

  1. 按照元数据组特性读取元数据组中的各个DataElement
  2. 按照数据组特性读取数据组中的各个DataElement
  3. 如果PixelData是压缩格式,则用相应的解压算法解压
  4. CT值转BMP,保存BMP图像

依赖:

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);//判断是否是SQbool 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数据组解析框架讲解。

这篇关于4 DICOM成像协议编码实现-元数据组解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/875543

相关文章

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并