LIDC-IDRI数据集的dicom文件XML标注读取
最近用到LIDC-IDRI数据集,这里说一下这个xml文件的标注怎么读取。
数据集的获取我是在paperswithcode上面找到的:https://paperswithcode.com/dataset/lidc-idri
首先来看一下这个标注文件的地址,它是附带在dicom数据里面的。如下图
尝试着打开一个,可以发现它的结构这样子的。
<?xml version="1.0" encoding="UTF-8"?>
<LidcReadMessage uid="1.3.6.1.4.1.14519.5.2.1.6279.6001.1307390687803.0" xmlns="http://www.nih.gov" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nih.gov http://troll.rad.med.umich.edu/lidc/LidcReadMessage.xsd">
<ResponseHeader>
<Version>1.8.1</Version>
<MessageId>-421198203</MessageId>
<DateRequest>2007-11-01</DateRequest>
<TimeRequest>12:30:44</TimeRequest>
<RequestingSite>removed</RequestingSite>
<ServicingSite>removed</ServicingSite>
<TaskDescription>Second unblinded read</TaskDescription>
<CtImageFile>removed</CtImageFile>
<SeriesInstanceUid>1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192</SeriesInstanceUid>
<DateService>2008-08-18</DateService>
<TimeService>02:05:51</TimeService>
<ResponseDescription>1 - Reading complete</ResponseDescription>
<StudyInstanceUID>1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178</StudyInstanceUID></ResponseHeader>
<readingSession>
<annotationVersion>3.12</annotationVersion>
<servicingRadiologistID>540461523</servicingRadiologistID>
<unblindedReadNodule>
<noduleID>Nodule 001</noduleID>
<characteristics>
<subtlety>5</subtlety>
<internalStructure>1</internalStructure>
<calcification>6</calcification>
<sphericity>3</sphericity>
<margin>3</margin>
<lobulation>3</lobulation>
<spiculation>4</spiculation>
<texture>5</texture>
<malignancy>5</malignancy>
</characteristics>
<roi>
<imageZposition>-125.000000 </imageZposition>
<imageSOP_UID>1.3.6.1.4.1.14519.5.2.1.6279.6001.110383487652933113465768208719</imageSOP_UID>
<inclusion>TRUE</inclusion>
<edgeMap>
<xCoord>312</xCoord>
<yCoord>355</yCoord>
</edgeMap>
<edgeMap>
<xCoord>311</xCoord>
<yCoord>356</yCoord>
</edgeMap>
<edgeMap>
<xCoord>310</xCoord>
<yCoord>357</yCoord>
</edgeMap>
这里省略了后面的很多,只展示了开始的一部分,下面我们来分析一下这个xml每个标识都代表了什么。
这里我们就要用到数据集中的annotations document文件,如下图这个:
这个文件描述了xml的标注方式以及每个标识符所代表的意思。
这里就不展开讲了,就讲几个关键的。
ResponseHeader
这个是头部分,记录了这个病例(也就是单个病人的CT图像)的信息。
readingSession
这个是某个医生的标注。这个数据集中每个病例都是由4个影像医生分别标注的。就是会有4个标注,每个ReadingSession表示一个医生的标注。
unblindedReadNodule
这个是医生采用非盲的方式标注的。这里我不是很懂这里的非盲指的是什么。医学上的盲的非盲指的是医生和病人相互不知道和相互知道的两种情况,这里的非盲我觉得可能是指医生是事先知道这个病人有肺结节的可能。
noduleID
这个是对每个肺结节的编号.其实没啥用。
characteristics
这个是对每个结节的描述。比如说大小啊啥的,具体可以去文件里面查。
roi
这个是每一层dcm文件的标注情况,这个就是roi具体所在的地方。其中imageZposition这个标识指的是这个roi所在的Z轴的位置,也就是它所在的slice。但这个值对应的并不是具体的那一层,而是和dcm文件里面的A字段相对应的,具体的方法可以用下面这个代码到,这面的打印中(0020, 1041) Slice Location这个字段的值-125.0就是对应的imageZposition,但他们取的小数点后精确度不一样,可以自己调一下:
import pydicom as dicom
#这个函数只能读取单个的dcm文件,不能读取整个目录
dimage = dicom.dcmread(fine_name)
print (dimage)
#(0020, 1040) Position Reference Indicator LO: 'SN'
#(0020, 1041) Slice Location DS: "-125.0"
#(0028, 0002) Samples per Pixel US: 1
#(0028, 0004) Photometric Interpretation CS: 'MONOCHROME2'
#(0028, 0010) Rows US: 512
print(dimage.SliceLocation)
#-125.000000
print(dimage.SOPInstanceUID)
#1.3.6.1.4.1.14519.5.2.1.6279.6001.110383487652933113465768208719
除此之外,imageSOP_UID这个字段也可以为当前的roi找到唯一对应的dcm文件,因为每一个dcm文件都有自己的唯一标识符,可以在对应的SOP Instance UID字段中找到,如上图.
好了,最后的edgeMap标识就表示坐标点了。xCoord和yCoord分别是x坐标和y坐标。
整个代码可以简单的如下:
import pydicom as dicom
import os
import cv2
import numpy as np
import lxml.etree as etree
#这个函数输入的是xml的路径,返回的是读取的坐标
#这里获得的坐标是所有层的,但只读取了单个病理医生标注的第一个结节,后面的全都不读,要是想要读的话可以改一下代码
def xml_reader(path):
anno = {}
xmlns = '{http://www.nih.gov}'
tree = etree.parse(path)
#for ann in tree.findall('./Annotation'):
assert tree.findall(xmlns + 'ResponseHeader')!=[] , '{}.has no ResponseHeader'.format(path)
Res = tree.findall(xmlns + 'ResponseHeader')[0]
#anno['SeriesInstanceUid'] = Res.findall(xmlns + 'SeriesInstanceUid')[0].text
#anno['StudyInstanceUID'] = Res.findall(xmlns + 'StudyInstanceUID')[0].text
#anno['session'] = []
if tree.findall(xmlns + 'readingSession')==[]: return {}
i = tree.findall(xmlns + 'readingSession')[0]
Nodules = []
if i.findall(xmlns + 'unblindedReadNodule')==[]: return {}
j = i.findall(xmlns + 'unblindedReadNodule')[0]
Nodule = []
for k in j.findall(xmlns + 'roi'):
roi = {}
roi['imageSOP_UID'] = k.findall(xmlns + 'imageSOP_UID')[0].text
roi['roi'] = []
for l in k.findall(xmlns + 'edgeMap'):
x = int(l.findall(xmlns + 'xCoord')[0].text)
y = int(l.findall(xmlns + 'yCoord')[0].text)
roi['roi'].append([x,y])
Nodule.append(roi)
Nodules.append(Nodule)
anno['session']=Nodules
return anno
roi_contour = xml_reader(xml_name)
#获取读取到的roi的坐标点
r = roi_contour['session'][0]
temp = np.zeros((512, 512), np.uint8)
mask = np.array(r['roi'],dtype=np.int)
#用cv2根据多边形画图。
temp = cv2.fillConvexPoly(temp, mask, (255,255,255))
好了xml的解析就结束了。