文章目录
前言
编码问题仿佛一个幽灵,在编程过程中总会时不时的出来纠缠你。最近在使用pydicom解析ROI的时候,又遇到了中文ROI解析乱码的问题,下面是我的解决经历。
遇到问题
我使用如下代码进行ROI名称提取
ds = pydicom.dcmread(os.path.join(root, file_path), force=True)
for struct_dic in ds.StructureSetROISequence:
roi_name=struct_dic.ROIName
但是,当出现名字为“中文”两个字的ROI的时候,roi_name显示的是乱码
ä¸Â文
这里roi_name是python中的str类型,我尝试输出encode之后的值
b‘\xc3\xa4\xc2\xb8\xc2\xad\xc3\xa6\xc2\x96\xc2\x87’
这就奇怪了,为什么两个字的“中文”最后会对应12字节的数据呢?
分析
encode实际上默认是utf-8编码,一个汉字对应三个字节,正常应该只有6个字节,实际是12字节,一定是经过了其他编解码处理。
我们知道,所谓的字符集就是字符和字节值的对应表,相同的字符,在不同的字符集上对应的字节值不同,并且,不同的字符集包含的字符个数也不同,例如gbk2312字符集里就没有어
字符。
现在我们有字符串中文
,经过一系列变换,会得到
b’\xc3\x83\xc2\xa4\xc3\x82\xc2\xb8\xc3\x82\xc2\xad\xc3\x83\xc2\xa6\xc3\x82\xc2\x96\xc3\x82\xc2\x87’
解决
尝试在google上查找\xc3\x83
,发现有类似的问题
>>> s = '\xE4\xB8\xAD\xE5\x9B\xBD\xE6\xB0\x91\xE7\x94\x9F\xE9\x93\xB6\xE8\xA1\x8C'
>>> s.encode('latin-1').decode('utf-8')
'中国民生银行'
提到了我们的那些乱码可能和latin-1字符集有关。那么就是说,原本应该被正确解码的字节值被错误地解码为latin-1字符集。
还记得刚刚我们打印出乱码的utf-8值一共12个字节吗,实际上我们查表知道,一个乱码正是对应了2个字节值,也就是说原来对应6个乱码字符。
而"中文"字符串utf-8编码后正是6个字节,再结合刚刚google到的信息,我们可以推测有可能是这6个字节的值被解码为latin-1字符集了,于是我们尝试:
'中文'.encode().decode('latin-1').encode()
结果为
b'\xc3\xa4\xc2\xb8\xc2\xad\xc3\xa6\xc2\x96\xc2\x87'
这正是我们原来的解析代码输出的内容!这么一来我们就知道乱码是如何形成的了:中文字符被utf-8编码,一共6个字节,然后被解码为latin-1字符集形成乱码。
这样一来,我们要还原原来的ROI名称,就是一个逆过程
x=b'\xc3\xa4\xc2\xb8\xc2\xad\xc3\xa6\xc2\x96\xc2\x87'
x.decode().encode('latin-1').decode()
回到dicom解析的问题上来,我怎么知道dicom到底是用什么字符集进行编码的呢?答案是(0008,0005)Specific Character Set Attribute
字段,该字段表明了dicom使用的字符集,如ISO_IR 100
代表latin-1字符集。
最后的问题就是如何把DICOM协议中的字符集表示和python中的编解码字符串相转换?查到pydicom有如下接口pydicom.charset.convert_encodings
因此,最终解析代码如下:
py_encoding=pydicom.charset.convert_encodings(ds.SpecificCharacterSet)[0]
roi_name=struct_dic.ROIName.encode(py_encoding).decode()
这样得到的roi_name就不再是乱码的字符串了
参考
1.https://*.com/questions/61054236/python3-decode-str-to-utf8
2.https://dicom.innolitics.com/ciods/ct-image/sop-common/00080005