]> Creatis software - gdcm.git/blob - Dicts/ParseDict.py
ENH: update parser for Intera/Philips document
[gdcm.git] / Dicts / ParseDict.py
1 #! /usr/bin/env python
2 """
3 Let's write our own python parser to clean up the pdf (after 
4 pdftotext of course). 
5 Instructions: run pdftotext like this:
6
7 $ pdftotext -f 9 -l 81 -raw -nopgbrk 04_06PU.PDF 04_06PU-3.txt
8
9 then run the python parser like this:
10
11 $ python ParseDict.py 04_06PU.txt dicomV3.dic
12 """
13 import re,os
14
15 """
16 PdfTextParser takes as input a text file (produced by pdftotext)
17 and create as output a clean file (ready to be processed) by
18 DicomV3Expander
19 Warning: PdfTextParser does not expand:
20 - (xxxx,xxxx to xxxx) xxxxxxxxxxxx
21 or
22 - (12xx, 3456) comment...
23
24 """
25 class PdfTextParser:
26   # Cstor
27   def __init__(self):
28     self._InputFilename = ''
29     self._OutputFilename = ''
30     self._Infile = 0
31     self._OutLines = []
32     self._PreviousBuffers = []
33
34   def SetInputFileName(self,s):
35     self._InputFilename = s
36
37   def SetOutputFileName(self,s):
38     self._OutputFilename = s
39   
40   # Function returning if s is a comment for sure
41   def IsAComment(self,s):
42     #print s,  len(s)
43     if s == "Tag Name VR VM":
44       return True
45     elif s == "PS 3.6-2003":
46       return True
47     elif s == "PS 3.6-2004":
48       return True
49     patt = re.compile('^Page [0-9]+$') 
50     if( patt.match(s) ):
51       return True
52     return False
53
54   def IsAStartingLine(self,s):
55     patt = re.compile('^\\([0-9a-fA-Fx]+,[0-9a-fA-F]+\\) (.*)$') 
56     if( patt.match(s) ):
57       return True
58     return False
59
60   def IsAFullLine(self,s):
61     patt = re.compile('^\\([0-9a-fA-Fx]+,[0-9a-fA-F]+\\) (.*) [A-Z][A-Z] [0-9]$')
62     if( patt.match(s) ):
63       return True
64     return False
65
66   # FIXME this function could be avoided...
67   def IsSuspicious(self,s):
68     l = len(s)
69     if l > 80:
70       return True
71     return False
72
73   def AddOutputLine(self,s):
74     assert not self.IsAComment(s)
75     self._OutLines.append(s + '\n')
76
77   def Open(self):
78     self._Infile = file(self._InputFilename, 'r')
79     for line in self._Infile.readlines():
80       line = line[:-1] # remove '\n'
81       if not self.IsAComment( line ):
82         if self.IsAStartingLine(line):
83           #print "Previous buffer:",self._PreviousBuffers
84           previousbuffer = ' '.join(self._PreviousBuffers)
85           if self.IsAStartingLine(previousbuffer):
86             if not self.IsSuspicious(previousbuffer):
87               self.AddOutputLine(previousbuffer)
88             else:
89               # this case should not happen if I were to rewrite the
90               # thing I should be able to clean that
91               #print "Suspicious:", previousbuffer
92               #print "List is:", self._PreviousBuffers
93               s = self._PreviousBuffers[0]
94               if self.IsAFullLine(s):
95                 # That means we have a weird line that does not start
96                 # as usual (xxxx,xxxx) therefore we tried constructing
97                 # a buffer using a the complete previous line...
98                 #print "Full line:", s
99                 self.AddOutputLine(s)
100                 s2 = ' '.join(self._PreviousBuffers[1:])
101                 #print "Other Full line:", s2
102                 self.AddOutputLine(s2)
103               else:
104                 # we have a suspicioulsy long line, so what that could
105                 # happen, let's check:
106                 if self.IsAFullLine(previousbuffer):
107                   self.AddOutputLine(previousbuffer)
108                 else:
109                   # This is the only case where we do not add
110                   # previousbuffer to the _OutLines
111                   print "Suspicious and Not a full line:", s
112           else:
113             if previousbuffer:
114               print "Not a buffer:", previousbuffer
115           # We can clean buffer, since only the case 'suspicious' +
116           # 'Not a full line' has not added buffer to the list
117           self._PreviousBuffers = []
118           # In all cases save the line for potentially growing this line
119           assert not self.IsAComment(line)
120           self._PreviousBuffers.append(line)
121         else:
122           #print "Not a line",line
123           assert not self.IsAComment(line)
124           self._PreviousBuffers.append(line)
125       else:
126         #print "Comment:",line
127         previousbuffer = ' '.join(self._PreviousBuffers)
128         if previousbuffer and self.IsAStartingLine(previousbuffer):
129           #print "This line is added:", previousbuffer
130           self.AddOutputLine( previousbuffer )
131         else:
132           #print "Line is comment:", line
133           print "Buffer is:", previousbuffer
134         # Ok this is a comment we can safely clean the buffer:
135         self._PreviousBuffers = []
136     self.Write()
137
138   def Write(self):
139     outfile = file(self._OutputFilename, 'w')
140     outfile.writelines( self._OutLines )
141     outfile.close()
142     self._Infile.close()
143     
144   # Main function to call for parsing
145   def Parse(self):
146     self.Open()
147
148 """
149 subclass
150 """
151 class UIDParser(PdfTextParser):
152   def IsAStartingLine(self,s):
153     patt = re.compile('^1.2.840.10008.[0-9.]+ (.*)$') 
154     if( patt.match(s) ):
155       return True
156     #print "Is Not:", s
157     return False
158
159   def IsAFullLine(self,s):
160     patt = re.compile('^1.2.840.10008.[0-9.]+ (.*) PS ?[0-9].1?[0-9]$') 
161     if( patt.match(s) ):
162       return True
163     patt = re.compile('^1.2.840.10008.[0-9.]+ (.*) Well-known frame of reference$') 
164     if( patt.match(s) ):
165       return True
166     patt = re.compile('^1.2.840.10008.[0-9.]+ (.*) \\(Retired\\)$') 
167     if( patt.match(s) ):
168       return True
169     return False
170
171   def IsAComment(self,s):
172     if PdfTextParser.IsAComment(self,s):
173       return True
174     # else let's enhance the super class
175     patt = re.compile('^SPM2 (.*) http(.*)$') 
176     if( patt.match(s) ):
177       return True
178     return False
179
180   def AddOutputLine(self,s):
181     if self.IsAFullLine(s):
182       return PdfTextParser.AddOutputLine(self,s)
183     print "Discarding:", s
184
185
186 """
187 TransferSyntaxParser
188 """
189 class TransferSyntaxParser(UIDParser):
190   def IsAFullLine(self,s):
191     patt = re.compile('^(.*) Transfer Syntax PS ?[0-9].1?[0-9]$') 
192     if patt.match(s):
193       return UIDParser.IsAStartingLine(self,s)
194     print "Not a TS:", s
195     return False
196     
197 """
198 Papyrus parser
199 pdftotext -f 19 -l 41 -raw -nopgbrk /tmp/Papyrus31Specif.pdf /tmp/Papyrus31Specif.txt 
200
201 I need to do a second pass for pages:
202 #29 since I need to find [0-9.]+
203 #40,41 since it start with number in two columns !!
204 """ 
205 class PapyrusParser(PdfTextParser):
206   def __init__(self):
207     self._PreviousPage = 0
208     self._PreviousNumber = 0
209     PdfTextParser.__init__(self)
210
211   def IsAStartingLine(self,s):
212     patt = re.compile('^[A-Za-z \'\(\)]+ +\\([0-9A-F]+,[0-9A-F]+\\) +(.*)$') 
213     if( patt.match(s) ):
214       return True
215     # After page 39, lines are like:
216     patt = re.compile('^[0-9x]+ [0-9xA-F]+ .*$') 
217     if( patt.match(s) ):
218       #print "PAge 39", s
219       return True
220     return False
221
222   def IsAFullLine(self,s):
223     patt = re.compile('^[A-Za-z \'\(\)]+ +\\([0-9A-F]+,[0-9A-F]+\\) +(.*)$') 
224     if( patt.match(s) ):
225       return True
226     # After page 39, lines are like:
227     patt = re.compile('^[0-9x]+ [0-9xA-F]+ .* [A-Z][A-Z] [0-9].*$') 
228     if( patt.match(s) ):
229       #print "PAge 39", s
230       return True
231     return False
232
233   def IsAComment(self,s):
234     # dummy case:
235     if s == 'Attribute Name Tag Type Attribute Description':
236       #print "Dummy", s
237       return True
238     patt = re.compile('^.*ANNEXE.*$')
239     if patt.match(s):
240       return True
241     # Indicate page #, spaces ending with only one number
242     # Sometime there is a line with only one number, we need to
243     # make sure that page # is strictly increasing
244     patt = re.compile('^[1-9][0-9]+$') 
245     if( patt.match(s) ):
246       p = eval(s)
247       if( p > self._PreviousPage):
248         #print "Page #", p
249         self._PreviousNumber = 0
250         self._PreviousPage = p
251         return True
252 #      else:
253 #        print "PAGE ERROR:", s
254     # Now within each page there is a comment that start with a #
255     # let's do the page approach wich reset at each page
256     patt = re.compile('^[0-9]+$') 
257     if( patt.match(s) ):
258       if( eval(s) > self._PreviousNumber):
259         #print "Number #", eval(s)
260         self._PreviousNumber = eval(s)
261         return True
262       #else:
263       #  print "ERROR:", s
264     return False
265
266   def AddOutputLine(self,s):
267     assert not self.IsAComment(s)
268     s = s.replace('\n','')
269     #print "REMOVE return:", s
270     patt = re.compile('^([A-Za-z \'\(\)]+) (\\([0-9A-F]+,[0-9A-F]+\\)) ([0-9C]+) (.*)$') 
271     m = patt.match(s)
272     ss = 'dummy (0000,0000) 0'
273     if m:
274       ss = m.group(2) + ' ' + m.group(3) + ' ' + m.group(1)
275     else:
276       patt = re.compile('^([A-Za-z \'\(\)]+) (\\([0-9A-F]+,[0-9A-F]+\\)) (.*)$') 
277       m = patt.match(s)
278       if m:
279         ss = m.group(2) + ' 0 ' + m.group(1)
280       else:
281         ss = s
282         # There is two case one that end with all capital letter
283         # explaining the 'DEFINED TERMS'
284         patt = re.compile('^[0-9x]+ [0-9xA-F]+ .* [A-Z][A-Z] [0-9] [A-Z, ]$') 
285         #patt = re.compile('^[0-9x]+ [0-9xA-F]+ .* [A-Z][A-Z] [0-9]|1\\-n [A-Z, |3.0]+$') 
286         #patt = re.compile('^[0-9x]+ [0-9xA-F]+ .* [A-Z][A-Z] [01n-] [A-Z, |3.0]+$') 
287         if patt.match(s):
288           print "Match", s
289           ss = ''
290     self._OutLines.append(ss + '\n')
291
292   def Open(self):
293     self._Infile = file(self._InputFilename, 'r')
294     for line in self._Infile.readlines():
295       line = line[:-1] # remove '\n'
296       if not self.IsAComment( line ):
297         if self.IsAStartingLine(line):
298           #print "Previous buffer:",self._PreviousBuffers
299           previousbuffer = ' '.join(self._PreviousBuffers)
300           if self.IsAFullLine(previousbuffer):
301             self.AddOutputLine(previousbuffer)
302           else:
303             if previousbuffer:
304               print "Not a buffer:", previousbuffer
305           # We can clean buffer, since only the case 'suspicious' +
306           # 'Not a full line' has not added buffer to the list
307           self._PreviousBuffers = []
308           # In all cases save the line for potentially growing this line
309           # just to be safe remove any white space at begining of string
310           assert not self.IsAComment(line)
311           self._PreviousBuffers.append(line.strip())
312         else:
313           #print "Not a line",line
314           assert not self.IsAComment(line)
315           # just to be safe remove any white space at begining of string
316           self._PreviousBuffers.append(line.strip())
317       else:
318         #print "Previous buffer:",self._PreviousBuffers
319         previousbuffer = ' '.join(self._PreviousBuffers)
320         if previousbuffer and self.IsAStartingLine(previousbuffer):
321           #print "This line is added:", previousbuffer
322           self.AddOutputLine( previousbuffer )
323 #        else:
324 #          #print "Line is comment:", line
325 #          print "Buffer is:", previousbuffer
326         # Ok this is a comment we can safely clean the buffer:
327         self._PreviousBuffers = []
328     self.Write()
329
330 """
331 This class is meant to expand line like:
332 - (xxxx,xxxx to xxxx) xxxxxxxxxxxx
333 or
334 - (12xx, 3456) comment...
335
336 """
337 class DicomV3Expander:
338   def __init__(self):
339     self._InputFilename = ''
340     self._OutputFilename = ''
341     self._OutLines = []
342
343   def SetInputFileName(self,s):
344     self._InputFilename = s
345
346   def SetOutputFileName(self,s):
347     self._OutputFilename = s
348  
349   # Function to turn into lower case a tag:
350   # ex: (ABCD, EF01) -> (abcd, ef01)
351   def LowerCaseTag(self,s):
352     #print "Before:", s[:-1]
353     patt = re.compile('^(\\([0-9a-fA-F]+,[0-9a-fA-F]+\\))(.*)$')
354     m = patt.match(s)
355     if m:
356       s1 = m.group(1)
357       s2 = m.group(2)
358       return s1.lower() + s2
359     else:
360       print "Impossible case:", s
361       os.sys.exit(1)
362
363   def AddOutputLine(self,s):
364     if s.__class__ == list:
365       for i in s:
366         self._OutLines.append(i + '\n')
367     else:
368       self._OutLines.append(s + '\n')
369
370   # Expand the line approriaetkly and also add it to the
371   # _OutLines list
372   def ExpandLine(self, s):
373     assert s[-1] == '\n'
374     s = s[:-1]  # remove \n
375     list = []
376     if self.NeedToExpansion(s, list):
377       self.AddOutputLine(list) # list != []
378     elif self.NeedXXExpansion(s, list):
379       self.AddOutputLine(list) # list != []
380     else:
381       self.AddOutputLine(self.LowerCaseTag(s))
382
383   # If line is like:
384   # (0020,3100 to 31FF) Source Image Ids RET
385   def NeedToExpansion(self,s, list):
386     patt = re.compile('^\\(([0-9a-fA-F]+),([0-9a-fA-F]+) to ([0-9a-fA-F]+)\\)(.*)$')
387     m = patt.match(s)
388     if m:
389       #print m.groups()
390       gr = m.group(1)
391       el_start = '0x'+m.group(2)
392       el_end = '0x'+m.group(3)
393       for i in range(eval(el_start), eval(el_end)):
394         el = hex(i)[2:]
395         l = '('+gr+','+el+')'+m.group(4)
396         list.append(l)
397       return True
398     return False
399
400   # If line is like:
401   # (50xx,1200) Number of Patient Related Studies IS 1
402   def NeedXXExpansion(self,s,list):
403     patt = re.compile('^\\(([0-9a-fA-F]+)xx,([0-9a-fA-F]+)\\)(.*)$')
404     m = patt.match(s)
405     if m:
406       #print m.groups()
407       gr_start = m.group(1)
408       el = m.group(2)
409       #el_start = '0x'+m.group(2)
410       #el_end = '0x'+m.group(3)
411       start = '0x'+gr_start+'00'
412       end   = '0x'+gr_start+'FF'
413       for i in range(eval(start), eval(end)):
414         gr = hex(i)[2:]
415         l = '('+gr+','+el+')'+m.group(3)
416         #print l
417         list.append(l)
418       return True
419     return False
420
421   def Write(self):
422     outfile = file(self._OutputFilename, 'w')
423     outfile.writelines( self._OutLines )
424     outfile.close()
425
426   def Expand(self):
427     infile = file(self._InputFilename,'r')
428     for line in infile.readlines():
429       # ExpandLine also LowerCase the line
430       self.ExpandLine(line) # l is [1,n] lines
431     self.Write()
432     infile.close()
433
434 """
435 Parse line from a philips document, line are like this:
436
437 Syncra Scan Type 2005,10A1 VR = CS, VM = 1
438 """
439 class InteraParser:
440   def __init__(self):
441     self._InputFilename = ''
442     self._OutputFilename = ''
443
444   def Reformat(self,s):
445     assert self.IsGood(s)
446     patt = re.compile("^([A-Za-z0-9 -]+) ([0-9A-Z]+),([0-9A-Z]+) VR = ([A-Z][A-Z]), VM = (.*)$")
447     m = patt.match(s)
448     if m:
449       dicom = m.group(2) + ' ' + m.group(3) + ' ' + m.group(4) + ' ' + m.group(5) + ' ' + m.group(1)
450       return dicom
451     else:
452       print "oops"
453
454   def IsGood(self,s):
455     patt = re.compile("^[A-Za-z0-9 -]+ [0-9A-Z]+,[0-9A-Z]+ VR = [A-Z][A-Z], VM = .*$")
456     if patt.match(s):
457       return True
458     print "Not good:", s
459     return False
460
461   def SetInputFileName(self,s):
462     self._InputFilename = s
463
464   def SetOutputFileName(self,s):
465     self._OutputFilename = s
466   
467   def Parse(self):
468     infile = file(self._InputFilename, 'r')
469     outLines = []
470     for line in infile.readlines():
471       print self.Reformat(line)
472       outLines.append( self.Reformat(line) + '\n' )
473     outfile = file(self._OutputFilename, 'w')
474     outfile.writelines( outLines )
475     outfile.close()
476  
477
478 if __name__ == "__main__":
479   argc = len(os.sys.argv )
480   if ( argc < 3 ):
481     print "Sorry, wrong list of args"
482     os.sys.exit(1) #error
483
484   inputfilename = os.sys.argv[1]
485   outputfilename = os.sys.argv[2]
486   tempfile = "/tmp/mytemp"
487   """
488   dp = PdfTextParser()
489   dp.SetInputFileName( inputfilename )
490   #dp.SetOutputFileName( outputfilename )
491   dp.SetOutputFileName( tempfile )
492   dp.Parse()
493
494   exp = DicomV3Expander()
495   exp.SetInputFileName( tempfile )
496   exp.SetOutputFileName( outputfilename )
497   exp.Expand()
498
499   dp = TransferSyntaxParser()
500   dp.SetInputFileName( inputfilename )
501   dp.SetOutputFileName( outputfilename )
502   dp.Parse()
503   dp = PapyrusParser()
504   dp.SetInputFileName( inputfilename )
505   dp.SetOutputFileName( outputfilename )
506   dp.Parse()
507   """
508
509   dp = InteraParser()
510   dp.SetInputFileName( inputfilename )
511   dp.SetOutputFileName( outputfilename )
512   dp.Parse()
513
514   #print dp.IsAStartingLine( "(0004,1212) File-set Consistency Flag US 1\n" )