DICOM文件添加私有Tag(DCMTK Private Tag)
DICOM文件插入私有tag
在处理dicom文件过程中,往往需要插入自定义的tag,并保存为dicom文件。在网上查资料,都比较少,经过一番探索,有点收获。与大家分享,希望能帮助到一些朋友,并共同探讨。
一 dicom文件tag
相关dicom介绍在这里不做赘述。dicom的Tag,分为两种:1.保留tag,为dicom自有字段,保存在偶数组号中(如 0x0008, 0x0010);2.private tag,为自定义字段,保存在奇数号码中如( 0x0029, 0x0011)。相关介绍在dicom标准的7.8。
7.8 私有数据元素
实现时可能需要不包含在标准数据元素中的信息通讯。私有数据元素需要包含这些信息。
私有数据元素与指定在7.1部分(即,数据元素标签字段,可选VR字段,长度字段和值字段)中的标准数据元素有相同的结构。使用在私有数据元素的元素标签中的组号码应该是一个奇数号码。私有数据元素应以数据元素标签的递增数字顺序包含在数据集中。私有数据元素的值字段应具有标准6.2部分中指定的VRs中的任意一种。
对于每一个信息对象定义或SOP类定义,按照说明在DICOM标准的第3部分和第4部分中的内容,特定数据元素是必需的。私有数据元素不能代替所需的标准数据元素。
7.8.1 私有数据元素标签
多个实现者定义带有相同(奇数)组号码的情况是可能的。为了避免冲突,私有元素将根据下面的规则来分配私有数据元素标签。
a) 编号为(gggg,0010-00FF)(gggg为奇数)的私有创作者数据元素用来存储由私人使用的组号码为gggg的一组元素。系统将给这一系列私有元素中的第一个未使用的(未赋值的)元素插入标识码。私有标识码的VR将成为LO,VM将等于1。
b) 私有创作者数据元素(gggg,0010)是等同于系统存储元素(gggg,1000-10FF)的1类数据元素,私有创作者数据元素(gggg,0011)等同于系统存储元素(gggg,1100-11FF),以此类推,直到私有创作者数据元素(gggg,00FF)等同于系统存储元素 (gggg,FF00-FFFF)。
c) 私有数据元素的编码器能够动态地将私有数据分配到私有组中的任一可利用块中,并详细说明分配所对应的私有创作者数据元素。私有数据的译码器能够由对应的私有创作者数据元素在私有组中的任何位置使用给定的私有创作者标识码识别存储块。
二 插入私有Tag
网上关于插入私有Tag的资料及demo较少,基本上都指向维基百科的一篇资料(得不到预期结果!!!) https://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata,代码如下:
1 #include "dcmtk/config/osconfig.h" 2 #include "dcmtk/dcmdata/dctk.h" 3 4 #define PRIVATE_CREATOR_NAME "Your Company Name" 5 6 #define PRIVATE_CREATOR_TAG 0x0029, 0x0010 7 #define PRIVATE_ELEMENT1_TAG 0x0029, 0x1000 8 #define PRIVATE_ELEMENT2_TAG 0x0029, 0x1010 9 #define PRIVATE_ELEMENT3_TAG 0x0029, 0x1020 10 11 #define PRV_PrivateCreator DcmTag(PRIVATE_CREATOR_TAG) 12 #define PRV_PrivateElement1 DcmTag(PRIVATE_ELEMENT1_TAG, PRIVATE_CREATOR_NAME) 13 #define PRV_PrivateElement2 DcmTag(PRIVATE_ELEMENT2_TAG, PRIVATE_CREATOR_NAME) 14 #define PRV_PrivateElement3 DcmTag(PRIVATE_ELEMENT3_TAG, PRIVATE_CREATOR_NAME) 15 16 void registerPrivateTags() 17 { 18 DcmDataDictionary &dict = dcmDataDict.wrlock(); 19 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT1_TAG, EVR_LO, "PrivateText", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 20 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT2_TAG, EVR_US, "PrivateInteger", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 21 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT3_TAG, EVR_OB, "PrivateBlob", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 22 dcmDataDict.unlock(); 23 } 24 25 void addPrivateElements(DcmItem &item) 26 { 27 if (!item.tagExists(PRV_PrivateCreator)) 28 { 29 item.putAndInsertString(PRV_PrivateCreator, PRIVATE_CREATOR_NAME); 30 item.putAndInsertString(PRV_PrivateElement1, "Some Text"); 31 item.putAndInsertUint16(PRV_PrivateElement2, 12345); 32 item.putAndInsertUint8Array(PRV_PrivateElement3, NULL /*data*/, 0 /*length*/); 33 } 34 } 35 36 int main() 37 { 38 DcmFileFormat fileformat; 39 fileformat.loadFile("test_in.dcm"); 40 registerPrivateTags(); 41 addPrivateElements(*fileformat.getDataset()); 42 fileformat.saveFile("test_out.dcm", EXS_LittleEndianExplicit); 43 fileformat.print(COUT); 44 return 0; 45 }
高高兴兴得使用这个demo,结果只能插入PRV_PrivateCreator 字段,做了很多次尝试,结果一样。刚开始怀疑自己哪里出错了,结果在bing上看到一个老哥说遇到跟我一样的问题,意识到可能是代码问题。只好再做其他尝试。
在看dicom标准过程中,看到一段话:
a) 编号为(gggg,0010-00FF)(gggg为奇数)的私有创作者数据元素用来存储由私人使用的组号码为gggg的一组元素。系统将给这一系列私有元素中的第一个未使用的(未赋值的)元素插入标识码。私有标识码的VR将成为LO,VM将等于1。
受到启发,系统将给这一系列私有元素中的第一个未使用的(未赋值的)元素插入标识码。是不是不能使用0x0029, 0x0010标签,应该留给系统插入标识码?(没有依据,纯属猜测)
于是,修改tag标签地址,不使用0x0010标签:
#define PRIVATE_CREATOR_TAG 0x0029, 0x0011
#define PRIVATE_ELEMENT1_TAG 0x0029, 0x0013
#define PRIVATE_ELEMENT2_TAG 0x0029, 0x0014
#define PRIVATE_ELEMENT3_TAG 0x0031, 0x0010
#define PRIVATE_ELEMENT4_TAG 0x0029, 0x0020
结果还真能插入多个tag,打印tag信息,可以看到新插入的几个tag。
当以为成功结果问题的时候,遇到了新的问题:插入int型数据不成功。测试代码如下:

1 #include "dcmtk/config/osconfig.h" 2 #include "dcmtk/dcmdata/dctk.h" 3 4 #define PRIVATE_CREATOR_NAME "Your Company Name" 5 6 #define PRIVATE_CREATOR_TAG 0x0029, 0x0011 7 #define PRIVATE_ELEMENT1_TAG 0x0029, 0x0013 8 #define PRIVATE_ELEMENT2_TAG 0x0029, 0x0014 9 #define PRIVATE_ELEMENT3_TAG 0x0031, 0x0010 10 #define PRIVATE_ELEMENT4_TAG 0x0029, 0x0020 11 12 #define PRV_PrivateCreator DcmTag(PRIVATE_CREATOR_TAG) 13 #define PRV_PrivateElement1 DcmTag(PRIVATE_ELEMENT1_TAG) 14 #define PRV_PrivateElement2 DcmTag(PRIVATE_ELEMENT2_TAG) 15 #define PRV_PrivateElement3 DcmTag(PRIVATE_ELEMENT3_TAG) 16 #define PRV_PrivateElement4 DcmTag(PRIVATE_ELEMENT4_TAG) 17 //#define PRV_PrivateElement1 DcmTag(PRIVATE_ELEMENT1_TAG, PRIVATE_CREATOR_NAME) 18 //#define PRV_PrivateElement2 DcmTag(PRIVATE_ELEMENT2_TAG, PRIVATE_CREATOR_NAME) 19 //#define PRV_PrivateElement3 DcmTag(PRIVATE_ELEMENT3_TAG, PRIVATE_CREATOR_NAME) 20 21 void registerPrivateTags() 22 { 23 DcmDataDictionary &dict = dcmDataDict.wrlock(); 24 dict.addEntry(new DcmDictEntry(PRIVATE_CREATOR_TAG, EVR_US, "PrivateText", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 25 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT1_TAG, EVR_US, "PrivateText", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 26 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT2_TAG, EVR_US, "PrivateInteger", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 27 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT3_TAG, EVR_OB, "PrivateBlob", 1, 1, "private", OFTrue, PRIVATE_CREATOR_NAME)); 28 dcmDataDict.unlock(); 29 } 30 31 void addPrivateElements(DcmItem &item) 32 { 33 if (!item.tagExists(PRV_PrivateCreator)) 34 { 35 item.putAndInsertString(PRV_PrivateCreator, "WHY"); 36 } 37 OFString PrivateCreator; 38 item.findAndGetOFString(PRV_PrivateCreator, PrivateCreator); 39 if (!item.tagExists(PRV_PrivateElement1)) 40 { 41 item.putAndInsertString(PRV_PrivateElement1, PRIVATE_CREATOR_NAME); 42 } 43 OFString PrivateElement1; 44 item.findAndGetOFString(PRV_PrivateElement1, PrivateElement1); 45 if (!item.tagExists(PRV_PrivateElement2)) 46 { 47 // item.putAndInsertUint16(PRV_PrivateElement2, 12); 48 item.putAndInsertOFStringArray(PRV_PrivateElement2, "'a','b'"); 49 } 50 if (!item.tagExists(PRV_PrivateElement3)) 51 { 52 //item.putAndInsertUint16(PRV_PrivateElement3, '10'); 53 item.putAndInsertUint16(PRV_PrivateElement3, 10,0,OFTrue); 54 } 55 Uint16 t = 1; 56 item.findAndGetUint16(PRV_PrivateElement3, t); 57 std::cout << "PRV_PrivateElement3: " << t; 58 59 // item.putAndInsertUint8Array(PRV_PrivateElement3, NULL /*data*/, 0 /*length*/); 60 } 61 62 int main() 63 { 64 DcmFileFormat fileformat; 65 fileformat.loadFile("D:\\te\\{832E0B3A-7509-45C2-88BD-3A0BC5C48D04}.dcm"); 66 // registerPrivateTags(); 67 addPrivateElements(*fileformat.getDataset()); 68 DcmDataDictionary &dict = dcmDataDict.wrlock(); 69 70 dict.addEntry(new DcmDictEntry(PRIVATE_ELEMENT4_TAG, EVR_US, "PrivateText", 1, 2, "private", OFTrue, PRIVATE_CREATOR_NAME)); 71 dcmDataDict.unlock(); 72 73 74 fileformat.getDataset()->putAndInsertString(PRV_PrivateElement4,"PRV_PrivateElement4"); 75 #define DCM_W DcmTagKey(0x0029, 0x0030) 76 fileformat.getDataset()->putAndInsertUint16(DCM_W, 99); 77 fileformat.saveFile("D:\\te\\test_out.dcm", EXS_LittleEndianExplicit); 78 fileformat.print(COUT); 79 return 0; 80 }
问题:打印结果,如上,插入的int型tag没有显示出来,插入不成功!!!
#define DCM_W DcmTagKey(0x0029, 0x0030)
fileformat.getDataset()->putAndInsertUint16(DCM_W, 99);
我查看了保留tag的int型tag,插入方式确实是上面的方式,这个可以通过修改某个tag值验证。
验证方式及结果:
如:fileformat.getDataset()->putAndInsertUint16(DCM_Rows, 99); 修改行数
可以看到tag信息中的,rows显示为修改值99,原始是704,与columns值一致。
查了不少资料,也没有解决,希望有知道的朋友留言探讨!谢谢
三 将过程数据保存在dicom文件中(序列化,反序列化)
最开始设想的就是,过程中的参数分为多给private tag插入,因此会遇到string,int,uint16等类型。后来发现,其实可以换个方法实现,将过程数据序列化后,存为一个私有tag。
过程为:1. 将过程文件序列化,存入dicom中
2. 读取dicom文件,解析该私有tag,读取其值,并反序列化,得到原始过程数据。
我采用的是boost序列方法,可参考 http://zh.highscore.de/cpp/boost/serialization.html,非常详细清晰。
生成dicom,https://blog.csdn.net/jiangsirl/article/details/7522986
结合起来,写了个demo

1 #include <boost/archive/text_oarchive.hpp> 2 #include <boost/archive/text_iarchive.hpp> 3 #include <iostream> 4 #include <sstream> 5 #include <string> 6 7 #include "dcmtk\dcmdata\dctk.h" 8 #include "DCMTK\dcmimgle\dcmimage.h" 9 10 11 #pragma comment(linker,"/NOD:LIBCMT") 12 #pragma comment(lib, "oflog.lib") 13 #pragma comment(lib, "ofstd.lib") 14 #pragma comment(lib, "dcmimage.lib") 15 #pragma comment(lib, "dcmdata.lib") 16 #pragma comment(lib, "oflog.lib") 17 #pragma comment(lib, "netapi32.lib") 18 #pragma comment(lib, "wsock32.lib") 19 20 std::stringstream ss; 21 using namespace std; 22 class person 23 { 24 public: 25 person() 26 { 27 } 28 29 person(int age) 30 : age_(age) 31 { 32 } 33 34 int age() const 35 { 36 return age_; 37 } 38 39 private: 40 friend class boost::serialization::access; 41 42 template <typename Archive> 43 void serialize(Archive &ar, const unsigned int version) 44 { 45 ar & age_; 46 } 47 48 int age_; 49 }; 50 51 void save() 52 { 53 boost::archive::text_oarchive oa(ss); 54 person p(31); 55 oa << p; 56 } 57 58 void load() 59 { 60 boost::archive::text_iarchive ia(ss); 61 person p; 62 ia >> p; 63 std::cout << p.age() << std::endl; 64 } 65 66 int main() 67 { 68 69 char uid[100]; 70 DcmFileFormat fileformat; 71 DcmMetaInfo *metainfo = fileformat.getMetaInfo(); 72 DcmDataset *dataset = fileformat.getDataset(); 73 74 //***meta group******/ 75 metainfo->putAndInsertString(DCM_FileMetaInformationVersion, "us test dcm file"); 76 metainfo->putAndInsertString(DCM_MediaStorageSOPClassUID, UID_RETIRED_UltrasoundImageStorage); 77 metainfo->putAndInsertString(DCM_MediaStorageSOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT)); 78 metainfo->putAndInsertString(DCM_TransferSyntaxUID, UID_LittleEndianExplicitTransferSyntax); 79 metainfo->putAndInsertString(DCM_ImplementationClassUID, "999.999"); 80 81 //***identifying group****/ 82 dataset->putAndInsertString(DCM_ImageType, "ORIGINAL\\PRIMARY\\TEE\\0011"); 83 dataset->putAndInsertString(DCM_SOPClassUID, UID_RETIRED_UltrasoundImageStorage);//UID_SecondaryCaptureImageStorage); 84 dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT)); 85 dataset->putAndInsertString(DCM_StudyID, "398474"); 86 dataset->putAndInsertString(DCM_StudyDate, "20100823"); 87 dataset->putAndInsertString(DCM_StudyTime, "080322"); 88 dataset->putAndInsertString(DCM_Modality, "US");//OT 89 90 dataset->putAndInsertString(DCM_Manufacturer, "ACME product"); 91 dataset->putAndInsertString(DCM_ReferringPhysicianName, "ANONY"); 92 dataset->putAndInsertString(DCM_StudyDescription, "STUDY description"); 93 dataset->putAndInsertString(DCM_SeriesDescription, "SERIES DESCRIPTION"); 94 dataset->putAndInsertString(DCM_StageNumber, "1"); 95 dataset->putAndInsertString(DCM_NumberOfStages, "1"); 96 dataset->putAndInsertString(DCM_ViewNumber, "1"); 97 dataset->putAndInsertString(DCM_NumberOfViewsInStage, "1"); 98 /***patient group*****/ 99 dataset->putAndInsertString(DCM_PatientID, "PatientID"); 100 dataset->putAndInsertString(DCM_PatientName, "PatientName"); 101 dataset->putAndInsertString(DCM_PatientSex, "M"); 102 dataset->putAndInsertString(DCM_PatientBirthDate, "20000302"); 103 104 /************************************************************************/ 105 /* acquisiton group */ 106 /************************************************************************/ 107 //DCM_ProtocolName 108 /************************************************************************/ 109 /* relation group */ 110 /************************************************************************/ 111 dataset->putAndInsertString(DCM_StudyInstanceUID, "999.999.2.19941105.112000"); 112 dataset->putAndInsertString(DCM_SeriesInstanceUID, "999.999.2.19941105.112000.2"); 113 dataset->putAndInsertString(DCM_SeriesNumber, "2"); 114 dataset->putAndInsertString(DCM_AccessionNumber, "1"); 115 //dataset->putAndInsertString(DCM_InstanceNumber,); 116 117 //调窗 118 //dataset->putAndInsertString(DCM_WindowCenter, "256"); 119 //dataset->putAndInsertString(DCM_WindowWidth, "128"); 120 121 const int width = 256; 122 const int height = 256; 123 dataset->putAndInsertString(DCM_InstanceNumber, "1"); 124 //dataset->putAndInsertString(DCM_PatientOrientation,"HFL"); 125 dataset->putAndInsertString(DCM_PhotometricInterpretation, "RGB"); 126 dataset->putAndInsertUint16(DCM_SamplesPerPixel, 3); 127 dataset->putAndInsertUint16(DCM_BitsAllocated, 8); 128 dataset->putAndInsertUint16(DCM_BitsStored, 8); 129 dataset->putAndInsertUint16(DCM_HighBit, 7); 130 dataset->putAndInsertUint16(DCM_PixelRepresentation, 0); 131 dataset->putAndInsertUint16(DCM_PlanarConfiguration, 0); 132 dataset->putAndInsertString(DCM_PixelAspectRatio, "4\\3"); 133 dataset->putAndInsertUint16(DCM_Rows, height);// 134 dataset->putAndInsertUint16(DCM_Columns, width);// 135 136 #define PRV_PrivateCreator DcmTag(0x0029, 0x0011) 137 dataset->putAndInsertString(PRV_PrivateCreator, "PRV_PrivateElement4"); 138 std::stringstream ss; 139 //boost::archive::text_oarchive oa(ss); 140 //string i = "1111111111111111111111111121213213421fdsafsdagdsagsfdbghfgjdfghjhgjfghjdgh"; 141 //oa << i; 142 143 boost::archive::text_oarchive oa(ss); 144 person p(31); 145 oa << p; 146 147 cout << "ss:" << ss.str() << endl; 148 dataset->putAndInsertString(PRV_PrivateCreator, ss.str().c_str()); 149 fileformat.print(COUT); 150 BYTE* pData = new BYTE[width*height * 3]; 151 memset(pData, 0, width*height * 3); 152 for (int y = 0; y < height; y++) { 153 //memset(pData+ y*width*3, y & 0xff0000,width*3); 154 for (int x = 0; x < width * 3; x++) 155 { 156 if (x % 3 == 0) 157 pData[y*width * 3 + x] = 0xff; 158 else 159 pData[y*width * 3 + x] = rand() % 256; 160 } 161 } 162 dataset->putAndInsertUint8Array(DCM_PixelData, pData, width*height * 3); 163 delete[] pData; 164 OFCondition status = fileformat.saveFile("d:\\test.dcm", 165 EXS_LittleEndianImplicit, EET_UndefinedLength, EGL_withoutGL); 166 if (status.bad()) 167 { 168 printf("\n cannot write dicom file"); 169 return false; 170 } 171 172 173 DcmFileFormat dfile; 174 OFCondition status1; 175 DcmMetaInfo *metainfo1; 176 177 status1 = dfile.loadFile("d:\\test.dcm"); 178 metainfo1 = dfile.getMetaInfo(); 179 DcmDataset *dset = dfile.getDataset(); 180 OFString privateTag; 181 string sPrivate; 182 dset->findAndGetOFString(PRV_PrivateCreator, privateTag); 183 sPrivate = privateTag.c_str(); 184 cout << sPrivate << endl; 185 186 string t = "1122222222222222222222222222222222222"; 187 stringstream st; 188 //st << t; 189 //cout << "st:" << st.str() << endl; 190 191 string as = "ab"; 192 stringstream ssPrivate; 193 194 ssPrivate << sPrivate; 195 196 //boost::archive::text_iarchive ia(ssPrivate); 197 //string adverse = "c"; 198 //ia >> adverse; 199 200 // cout << "反序列化:" << adverse << endl; 201 202 boost::archive::text_iarchive ia(ssPrivate); 203 person p1; 204 ia >> p1; 205 std::cout << p1.age() << std::endl; 206 207 208 save(); 209 load(); 210 return 0; 211 }
打印结果:
可以看到,序列化输入的为age=31,反序列化得到的age也是31。说明成功将一个类对象,序列化后存入dicom文件,解析该文件的tag,反序列化后可以得到原始值。
大功告成!将自定义结构数据成功存入privateTag中!
转发链接:https://i.cnblogs.com/EditPosts.aspx?postid=9338142
欢迎探讨!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~