|
Dicom作为医学影像的常见数据格式,是每个深耕于医疗AI的同学无法跳过的一个坑。虽然我只是一名扎根于算法部署方面的小白。但是也不可避免地接触到这类数据。这不,最近接到算法同学给出的算法,需要我自己找公开数据集进行测试。可是Dicom数据集并不常见(PS:测了1000张还嫌不够,大无语),因此只能将目光聚焦于PNG、JPG类型的数据集(直接用PNG、JPG训练的除外)。 但是PNG、JPG类型的数据转Dicom并不容易,一不小心你就会收获“非标准Dicom”,网上的一些教程我也尝试了,很遗憾:转出来的Dicom不是黑不溜秋,就是无法识别。要么就是c++写的,编译来编译去,令人心烦。也尝试过用现成的Dicom数据,然后使用PNG、JPG的Data替换其中的PixelData。但是都无功而返! 于是乎,我潜心钻研(东Copy西Copy),完成了这份python版本的PNG、JPG转Dicom。目录1.Dicom数据格式简介2.PNG、JPG转Dicom(以PNG为例)3.进一步完善Dicom 4.结果展示1.Dicom数据格式简介 首先,在你尝试着将PNG、JPG类型的数据转换成Dicom数据之前,你可能需要浅浅了解一下Dicom数据的基本格式。(1)preamble(前导):不重要,主要是为了向后兼容性和可扩展性而保留的若干个字节。(2)prefix(前缀):不重要,主要是确认该文件是否符合DICOM标准。前导和前缀是可选的,对于DICOM文件来说,并不是必需的。(3)FileMetaInformation(文件元信息头):重要!!!文件元信息头是DICOM文件的必要部分,其中包含了一些关键信息,如DICOM版本号、文件字节顺序、数据元素编码方式等。(4)DataElements(数据元素):重要!!!是DICOM文件中包含的实际医学图像和相关信息的部分。2.PNG、JPG转Dicom(以PNG为例) OK,知道了Dicom的数据结构,我们就能够针对主要的部分来转换我们的PNG、JPG。废话不多说,上代码!如果你不想看接下来的分析,你只需要修改main函数中的路径即可。importosimportpydicomfromPILimportImagedefpng_to_dicom(input_png_path,output_dcm_path,patient_name="Anonymous",study_description="PNGtoDICOM"):forfileNamesinos.listdir(input_png_path):input_filename=os.path.basename(fileNames).split('.')[0]output_filename=input_filename+".dcm"input_filepath=input_png_path+fileNamesoutput_dcmpath=output_dcm_path+output_filename#读取PNG图像img=Image.open(input_filepath)#将PNG图像转换为灰度图像(单通道)pixel_array=img.convert("L")#创建一个空的FileDataset对象,并添加DICOM数据集元素ds=pydicom.dataset.FileDataset(output_dcm_path,{},file_meta=pydicom.dataset.Dataset())#创建文件元信息头对象#添加DICOM文件元信息头ds.file_meta.FileMetaInformationGroupLength=184ds.file_meta.FileMetaInformationVersion=b'\x00\x01'ds.file_meta.MediaStorageSOPClassUID='1.2.840.10008.5.1.4.1.1.1.1'ds.file_meta.MediaStorageSOPInstanceUID='1.2.410.200048.2858.20230531153328.1.1.1'ds.file_meta.TransferSyntaxUID='1.2.840.10008.1.2'ds.file_meta.ImplementationClassUID='1.2.276.0.7230010.3.0.3.5.4'ds.file_meta.ImplementationVersionName='ANNET_DCMBK_100'#添加DICOM数据集元素ds.PatientName=patient_nameds.StudyDescription=study_descriptionds.Columns,ds.Rows=img.sizeds.SamplesPerPixel=1ds.BitsAllocated=8ds.BitsStored=8ds.HighBit=7ds.PixelRepresentation=0#数据显示格式ds.PhotometricInterpretation="MONOCHROME2"ds.PixelData=pixel_array.tobytes()#直接使用灰度图像的字节数据#保存DICOM数据集到文件ds.is_little_endian=Trueds.is_implicit_VR=True#使用隐式VRds.save_as(output_dcmpath)print(output_dcmpath)if__name__=="__main__":#输入PNG图像路径和输出DICOM图像路径input_png_path="Your_Input_PNG_Path"output_dcm_path="Your_Output_Dicom_Path"#将PNG转换为DICOMpng_to_dicom(input_png_path,output_dcm_path)让我们来详细分析一下这部分代码:(1)FileMetaInformationGroupLength:指定FileMetaInformation部分的长度,随意啦,别太离谱就行。(2)FileMetaInformationVersion:表示FileMetaInformation部分的版本号。(3)MediaStorageSOPClassUID:定义了影像的数据类型,每种类型有唯一的UID标识。如“1.2.840.10008.5.1.4.1.1.1.1”代表的是“DigitalX-RayImageStorage-ForPresentation”(4)MediaStorageSOPClassUID:唯一标识一个特定的影像数据实例。(5)TransferSyntaxUID:表示DICOM图像数据的传输语法,它指定了数据在网络传输中的编码方式。每种方式有唯一的UID标识,比如“1.2.840.10008.1.2”代表的是“ImplicitVRLittleEndian”。(6)ImplementationClassUID:用于标识实现DICOM标准的应用程序或设备的唯一标识符。(7)ImplementationVersionName: 实现DICOM标准的应用程序或设备的版本名称或标识。 看到这里你可能会问“你这乱七八糟的一堆数字,我怎么知道什么意思?”聪明的我早就想到了,首先随便选一张标准的Dicom数据,然后执行如下代码:importpydicomdataset=pydicom.dcmread("Your_Dicom_Path",force=True)print(dataset.file_meta) 然后你会看到下面一堆信息,如果你想更换MediaStorageSOPClassUID和TransferSyntaxUID,那你就要自己去查对应的UID喽,所以以下内容我不建议你自己换,除非你知道你要干嘛:3.进一步完善Dicom 哈哈哈哈,没想到还有吧!实际上,通过第2步,你已经可以获得一张用于展示的Dicom数据格式了。但是仅此而已,如果你想要做算法或者跟我一样,去验证别人的算法。那么,这一步是必不可少的。 在第2步中,我们为新的Dicom添加了FileMetaInformation(文件元信息头)和部分DataElements(主要是PixelData)。因此这份Dicom是可以正常被读取、浏览的。但是,如果是用于算法训练或者验证算法,是需要确保这份Dicom数据的唯一性的。 为了便于理解,确保Dicom数据唯一性的代码,我另起了一个新的py文件:importosimportpydicom#源文件夹和目标文件夹路径source_folder='Your_Input_Dicom_Path'target_folder='Your_Output_Dicom_Path'patient_pid=20230726001accession_number=202307261001study_uid=2023072620001seriesNumber=1seriesInstanceUID="1.2.410.200048.2858.20230529094313.1"modality="CR"pixelSpacing=[0.160145,0.160114]instanceNumber=1bodyPartExamined="CHEST"#遍历源文件夹中的文件forfilenameinos.listdir(source_folder):iffilename.endswith('.dcm'):#构建源文件路径和目标文件路径source_file=os.path.join(source_folder,filename)target_file=os.path.join(target_folder,filename)#加载源DCM文件dcm_data=pydicom.dcmread(source_file,force=True)#添加患者PID、AccessionNumber和StudyUID等信息dcm_data.PatientID=str(patient_pid)dcm_data.AccessionNumber=str(accession_number)dcm_data.StudyInstanceUID=str(study_uid)dcm_data.SeriesNumber=seriesNumberdcm_data.SeriesInstanceUID=seriesInstanceUIDdcm_data.Modality=modalitydcm_data.PixelSpacing=pixelSpacingdcm_data.BodyPartExamined=bodyPartExamineddcm_data.InstanceNumber=instanceNumber#将文件名作为患者名file_name_without_extension=os.path.splitext(filename)[0]dcm_data.PatientName=file_name_without_extension#保存修改后的DCM文件到目标文件夹dcm_data.save_as(target_file)#递增计数器patient_pid+=1accession_number+=1study_uid+=1else:print("error!")同样的,我们来详细解析以下这部分代码。(1)patient_pid:患者的唯一标识符,你怎么开心怎么写。(2)accession_numbe:分配给患者检查的唯一标识号码,唯一地标识特定的检查或一组医学影像,你怎么开心怎么写。(3)study_uid:对应医学影像研究的ID,你怎么开心怎么写。(4)seriesNumber:标识图像所属系列的编号,建议按照我这个来。(5)seriesInstanceUID:唯一标识一个影像系列,建议按照我这个来,或者你找一张标准的Dicom,参考它怎么写。(6)modality:用于获取图像的影像学模态,建议按照我这个来,或者你找一张标准的Dicom,参考它怎么写。(7)pixelSpacing:像素在行和列方向上的物理间距,建议按照我这个来,或者你找一张标准的Dicom,参考它怎么写。(8)instanceNumber:分配给图像中个别实例的唯一编号,通常用于区分一个系列中的不同图像,你怎么开心怎么写。(9)bodyPartExamined:检查部位,根据实际写,你也可以不写。4.结果展示 需要说的是,PNG、JPG亦或是其它类型的数据,在转成Dicom的过程中不可避免地会出现一定的损失。如果在Dicom数据充足的情况下,无论是算法训练还是验证,都建议使用Dicom(直接用PNG训练的除外)。PNG、JPG转Dicom实属无奈之举啊!
|
|