scanOrthancQuick.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #labkey/orthanc interface
  2. #scans the orthanc internal database and fills labkey table
  3. #"Orthanc" section expected in the configuration file, with
  4. #"queryName": query to be filled for each image
  5. #"demographicsQuery": query to be filled for every patient
  6. #"schemaName": the schema both queries are part of
  7. #"project": name of the project under labkey that collects Orthanc data,
  8. # typically named Orthanc or similar
  9. #"participantField": sorting participant field label in labkey study
  10. import os
  11. import json
  12. import re
  13. import sys
  14. def selectPatient(orthancId,globalId,pars):
  15. #pars is database portion of pars
  16. if not pars.get('filter',None):
  17. return True
  18. qf=pars['filter']
  19. #AND of possible filters
  20. for f in qf:
  21. #does not start with
  22. if f=='does_not_start_with':
  23. if globalId.startswith(qf[f]):
  24. return False
  25. return True
  26. def adjustId(fullId):
  27. idLength=len(fullId)
  28. if idLength<33:
  29. return fullId,fullId
  30. shortId=fullId.replace('-','')
  31. return fullId,shortId
  32. def main(parameterFile):
  33. fhome=os.path.expanduser('~')
  34. fsetup=os.path.join(fhome,'.labkey','setup.json')
  35. with open(fsetup,'r') as f:
  36. setup=json.load(f)
  37. with open(parameterFile) as f:
  38. pars=json.load(f)
  39. sys.path.insert(0,setup['paths']['nixWrapper'])
  40. import nixWrapper
  41. nixWrapper.loadLibrary("labkeyInterface")
  42. import labkeyInterface
  43. import labkeyDatabaseBrowser
  44. nixWrapper.loadLibrary('orthancInterface')
  45. import orthancInterface
  46. import orthancDatabaseBrowser
  47. try:
  48. networkSetup=pars['networkSetup']
  49. except KeyError:
  50. networkSetup='network.json'
  51. fconfig=os.path.join(fhome,'.labkey',networkSetup)
  52. if not os.path.isfile(fconfig):
  53. print('Failed to find network configuration {}'.format(fconfig))
  54. return
  55. net=labkeyInterface.labkeyInterface()
  56. net.init(fconfig)
  57. db=labkeyDatabaseBrowser.labkeyDB(net)
  58. onet=orthancInterface.orthancInterface()
  59. onet.init(fconfig)
  60. odb=orthancDatabaseBrowser.orthancDB(onet)
  61. opars=pars['Orthanc']
  62. project=opars['project']
  63. participantField=opars['participantField']
  64. patients=odb.getPatients()
  65. print('Got: {} patients in Orthanc.'.format(len(patients)))
  66. #equivalent for labkey side?
  67. dsDemo=db.selectRows(project,opars['schemaName'],\
  68. opars['demographicsQuery'],[])
  69. dsPatients=[row['OrthancId'] for row in dsDemo['rows']]
  70. print('Got {} patients in LabKey'.format(len(dsPatients)))
  71. pMissing=[p for p in patients if p not in dsPatients]
  72. print('Missing : {}'.format(len(pMissing)))
  73. #we need details for all patients (some might have just a study uploaded
  74. #this takes a while since demo table is not updated.
  75. #pdata={p:odb.getPatientData(p) for p in patients}
  76. #print('Got data for missing patients')
  77. #return
  78. #update patient data for missing patients
  79. prows=[]
  80. i=0
  81. for p in pMissing:
  82. #dicom=pdata[p]['MainDicomTags']
  83. try:
  84. dicom=odb.getPatientData(p)['MainDicomTags']
  85. except TypeError:
  86. continue
  87. row={}
  88. fullId,shortId=adjustId(dicom['PatientID'])
  89. row[participantField]=shortId
  90. idLength=len(shortId)
  91. row['birthDate']=dicom['PatientBirthDate']
  92. row['PatientName']=dicom['PatientName']
  93. row['OrthancId']=p
  94. row['fullId']=fullId
  95. prows.append(row)
  96. oid=row[participantField]
  97. print(f'Adding [{oid}]: {p}')
  98. resp=db.modifyRows('insert',project,opars['schemaName'],
  99. opars['demographicsQuery'],[row])
  100. try:
  101. print('[{}] Error: {}'.format(idLength,resp['exception']))
  102. except KeyError:
  103. pass
  104. #if i==0:
  105. # return
  106. #db.modifyRows('insert',project,opars['schemaName'],\
  107. # opars['demographicsQuery'],prows)
  108. n=len(patients)
  109. i=0
  110. prows.extend(dsDemo['rows'])
  111. patientMap={r['OrthancId']:r[participantField] for r in prows}
  112. patients=[p for p in patients if selectPatient(p,patientMap[p],pars['Database'])]
  113. print('After filter: {}/{}'.format(len(patients),n))
  114. n=len(patients)
  115. #download the full images dataset (~1000 rows)
  116. ds=db.selectRows(project,opars['schemaName'],opars['queryName'],[])
  117. print('Loaded image dataset ({} rows)'.format(len(ds['rows'])))
  118. for p in patients:
  119. pdata=odb.getPatientData(p)
  120. #dicom=pdata[p]['MainDicomTags']
  121. #patientId=dicom['PatientID']
  122. patientId=patientMap[p]
  123. #get all studies for pateint
  124. #qfilter={'variable':participantField,'value':patientId,'oper':'eq'}
  125. dsStudies=[row['orthancStudy'] for row in ds['rows'] if row[participantField]==patientId]
  126. #number of entries for patient (if new must be inserted)
  127. seqNum=len(dsStudies)
  128. #unique
  129. dsStudies=list(set(dsStudies))
  130. studies=pdata['Studies']
  131. pHex=p.split('-')
  132. print("[{:>3d}/{:>3d}] ID: {:>10s} [{}] Studies: {:>2}/{:>2} Entries in DB: {:>4d}".format(i,n,patientId,pHex[-1],len(dsStudies),len(studies),seqNum))
  133. missingStudies=[s for s in studies if s not in dsStudies]
  134. #print('Missing studies: {}'.format(len(missingStudies)))
  135. srows=[]
  136. for s in missingStudies:
  137. sdata=odb.getStudyData(s)
  138. sdicom=sdata['MainDicomTags']
  139. sid=sdicom['StudyInstanceUID']
  140. #print('Data: {}'.format(sdata))
  141. #this is try/except protetcted in previous version...
  142. sdate="19700101"
  143. try:
  144. sdate=sdicom['StudyDate']
  145. except KeyError:
  146. pass
  147. #continue
  148. print('\tStudy[{}]: {}/{}'.format(sdate,s,sid))
  149. for se in sdata['Series']:
  150. sedata=odb.getSeriesData(se)
  151. sedicom=sedata['MainDicomTags']
  152. seid=sedicom['SeriesInstanceUID']
  153. #print('Data: {}'.format(sedata))
  154. seDesc="NONE"
  155. try:
  156. seDesc=sedicom['SeriesDescription']
  157. except KeyError:
  158. pass
  159. #replace letters that might trip the database
  160. spanishOAcute=''.join([chr(3619),chr(3603)])
  161. spanishAAcute=''.join([chr(3619),chr(3585)])
  162. seDesc=re.sub(spanishOAcute,'o',seDesc)
  163. seDesc=re.sub(spanishAAcute,'a',seDesc)
  164. print('\t\tSeries[{}]: {}/{}'.format(seDesc,se,seid))
  165. #qfilter={'variable':'orthancSeries','value':se,'oper':'eq'}
  166. #ds=db.selectRows(project,opars['schemaName'],\
  167. # opars['queryName'],[qfilter])
  168. #count existing entries for patient
  169. #qfilter={'variable':participantField,'value':patientId,'oper':'eq'}
  170. #ds=db.selectRows(project,opars['schemaName'],\
  171. #opars['queryName'],[qfilter])
  172. # seqNum=len(ds['rows'])
  173. #create new row
  174. row={}
  175. row[participantField]=patientId
  176. row['sequenceNum']=seqNum
  177. row['orthancSeries']=se
  178. row['dicomStudy']=sid
  179. row['orthancStudy']=s
  180. row['dicomSeries']=seid
  181. row['studyDate']=sdate
  182. row['seriesDescription']=seDesc
  183. srows.append(row)
  184. seqNum+=1
  185. #upload updates
  186. if len(srows)>0:
  187. db.modifyRows('insert',project,opars['schemaName'],\
  188. opars['queryName'],srows)
  189. i+=1
  190. print("Done")
  191. if __name__=="__main__":
  192. main(sys.argv[1])