面向眼科医生的正则表达式(0)-简介

正则表达式是一种程序员用来在大量字符里进行定位的方法. 算得上是处理字符串时的解剖学了.

最近解锁这个技能以后, 如同拿到了一把雷神之锤, 非常好用看什么都想砸一下.

有很多的眼科检查设备厂商, 以蔡司为代表, 非常邪恶. 不肯开放自己的数据接口, 只将数据以pdf的形式发送出来. 要想取得其中的数据, 很多医生恐怕是人眼OCR手动输入, 或者一部分自动化程度高一点的是把pdf转换成纯文本txt文件, 再从txt中复制粘贴. 再或者是类似同仁医院那样霸气地使用OCR处理整个pdf文件.

用OCR处理整个pdf会有一些问题, 我现在还在尝试解决之中, 比如对于视野数据, 经常会出现-2 0这样的数字, 很容易就被识别成了-20, 还有可能把r识别成n之类. 这其中恐怕还有一些技巧.

先把PDF转换成纯文本就不会有识别错误的问题, 但引入的问题是格式的混乱, 不知道txt中的文字是对应到pdf上的哪个位置, 这时候就需要正则表达式帮忙了.

还是以视野数据为例, 我在提取视野数据时发现,

False POS Errors: False NEG Errors: Test Duration: Blind Spot Central 1/13 15% XX 5% GHT: VFI: MD: PSD: Outside Normal Limits 76% -8.33 dB P < 0.5% 10.06 dB P < 0.5%

实际上对应的关系是:

  • False NEG Errors: 15%
  • False NEG Errors: 5%
  • VFI: 76%
  • MD: -8.33 dB P < 0.5%
  • PSD: 10.06 dB P < 0.5%

每一个视野报告的pdf中, 这一段除了数字不同, 其他都是类似的. 那么要把这些数字提取出来, 就要先定位, 然后剪切出想要的片段. 这时候就需要正则表达式了

在python中, 处理正则表达式的工具包叫做re, 在其他语言中也有相应的工具包. 下面以python为例进行讲解.

In [1]:
import re
string="False POS Errors: False NEG Errors: Test Duration: Blind Spot Central 1/13 15% XX 5% GHT: VFI: MD: PSD: Outside Normal Limits 76% -8.33 dB P < 0.5% 10.06 dB P < 0.5%"

观察一下这些数据我们会发现一些pattern, 比如这些数据都跟%有关, MD和PSD的单位是dB.

曹安民教授讲课时说过"眼科就是pattern recognition, 如果不能识别出pattern来, 你就hopeless了"

False NEG Errors, False NEG Errors, VFI的pattern很明显, 数字然后紧跟着%. 要描述这个pattern可以用以下规则:

  • [0-9], 表示这是一个字符, 取值可以选取从0到9的数字
  • [0-9]+, 表示这里有数字, 至少有一位数, 可以有多位数
  • [0-9]+%, 表示这里有数字, 至少有一位数, 可以有多位数, 后面紧跟着一个%号.

我们可以使用python re工具包里面的findall函数来测试一下:

In [5]:
re.findall("\d+%",string)
Out[5]:
['15%', '5%', '76%', '5%', '5%']

很容易就把False NEG Errors, False NEG Errors, VFI的数据给分出来了.

那么MD和PSD也类似, 都有以dB为结尾的特征,

  • [0-9]+ dB: 有至少一位数字, 可以有多位, 以空格和dB作为结尾
In [6]:
re.findall("[0-9]+ dB",string)
Out[6]:
['33 dB', '06 dB']

这时候没有提取出-8.33, 只是提取出了33 dB, 需要考虑到数字中还可能有正负号和小数点. 所以会更复杂一些:

  • \- 表示-号, 因为-号在正则表达式中还有其他的含义, 所以要前面加一个\表示区分, 类似的还有\.表示小数点
  • \-*表示-号这个字符可以出现0次, 也可以出现多次.
    • 用*来表示出现0次或多次,
    • 用+来表示出现1次或多次

那么-8.33 dB和10.06 dB的pattern是: 有或者没有-号, 然后跟0个或者多个数字, 再接着是0个或者1个小数点, 然后是一定会有的数字, 再后面是空格和dB. 用正则表达式写出来就是:

  • \-*[0-9]*\.*[0-9]+ dB
In [7]:
re.findall("\-*[0-9]*\.*[0-9]+ dB",string)
Out[7]:
['-8.33 dB', '10.06 dB']

现在已经可以提取出了数字和后面的单位, 但这还是字符, 要处理成数据还要再拆分, 也有简单的方案, 就是用()来给搜索的pattern分组: (-[0-9].*[0-9]+)描述的是数据部分, ( dB)描述的是单位部分.

In [8]:
re.findall("(\-*[0-9]*\.*[0-9]+)( dB)",string)
Out[8]:
[('-8.33', ' dB'), ('10.06', ' dB')]

这样就容易进行后续的处理了.

一些常用的正则表达式:

我处理了写文档, 觉得常用的也不多:

  • [0-9]或者\d表示数字
  • [a-z]表示小写字母
  • [A-Z]表示大写字母
  • \n\t表示换行和tab
  • +和*表示前面的字符可以出现多次, 其中+是表示至少出现1次, *没有这个限制

下一篇的练习中, 还会再讲解一些更好用的技巧, 可以处理更为复杂的pattern.

In [ ]: