#labkey/orthanc interface #scans the orthanc internal database and fills labkey table #"Orthanc" section expected in the configuration file, with #"queryName": query to be filled for each image #"demographicsQuery": query to be filled for every patient #"schemaName": the schema both queries are part of #"project": name of the project under labkey that collects Orthanc data, # typically named Orthanc or similar #"participantField": sorting participant field label in labkey study import os import json import re import sys def selectPatient(orthancId,globalId,pars): #pars is database portion of pars if not pars.get('filter',None): return True qf=pars['filter'] #AND of possible filters for f in qf: #does not start with if f=='does_not_start_with': if globalId.startswith(qf[f]): return False return True def adjustId(fullId): idLength=len(fullId) if idLength<33: return fullId,fullId shortId=fullId.replace('-','') return fullId,shortId def main(parameterFile): fhome=os.path.expanduser('~') fsetup=os.path.join(fhome,'.labkey','setup.json') with open(fsetup,'r') as f: setup=json.load(f) with open(parameterFile) as f: pars=json.load(f) sys.path.insert(0,setup['paths']['nixWrapper']) import nixWrapper nixWrapper.loadLibrary("labkeyInterface") import labkeyInterface import labkeyDatabaseBrowser nixWrapper.loadLibrary('orthancInterface') import orthancInterface import orthancDatabaseBrowser try: networkSetup=pars['networkSetup'] except KeyError: networkSetup='network.json' fconfig=os.path.join(fhome,'.labkey',networkSetup) if not os.path.isfile(fconfig): print('Failed to find network configuration {}'.format(fconfig)) return net=labkeyInterface.labkeyInterface() net.init(fconfig) db=labkeyDatabaseBrowser.labkeyDB(net) onet=orthancInterface.orthancInterface() onet.init(fconfig) odb=orthancDatabaseBrowser.orthancDB(onet) opars=pars['Orthanc'] project=opars['project'] participantField=opars['participantField'] patients=odb.getPatients() print('Got: {} patients in Orthanc.'.format(len(patients))) #equivalent for labkey side? dsDemo=db.selectRows(project,opars['schemaName'],\ opars['demographicsQuery'],[]) dsPatients=[row['OrthancId'] for row in dsDemo['rows']] print('Got {} patients in LabKey'.format(len(dsPatients))) pMissing=[p for p in patients if p not in dsPatients] print('Missing : {}'.format(len(pMissing))) #we need details for all patients (some might have just a study uploaded #this takes a while since demo table is not updated. #pdata={p:odb.getPatientData(p) for p in patients} #print('Got data for missing patients') #return #update patient data for missing patients prows=[] i=0 for p in pMissing: #dicom=pdata[p]['MainDicomTags'] try: dicom=odb.getPatientData(p)['MainDicomTags'] except TypeError: continue row={} fullId,shortId=adjustId(dicom['PatientID']) row[participantField]=shortId idLength=len(shortId) row['birthDate']=dicom['PatientBirthDate'] row['PatientName']=dicom['PatientName'] row['OrthancId']=p row['fullId']=fullId prows.append(row) oid=row[participantField] print(f'Adding [{oid}]: {p}') resp=db.modifyRows('insert',project,opars['schemaName'], opars['demographicsQuery'],[row]) try: print('[{}] Error: {}'.format(idLength,resp['exception'])) except KeyError: pass #if i==0: # return #db.modifyRows('insert',project,opars['schemaName'],\ # opars['demographicsQuery'],prows) n=len(patients) i=0 prows.extend(dsDemo['rows']) patientMap={r['OrthancId']:r[participantField] for r in prows} patients=[p for p in patients if selectPatient(p,patientMap[p],pars['Database'])] print('After filter: {}/{}'.format(len(patients),n)) n=len(patients) #download the full images dataset (~1000 rows) ds=db.selectRows(project,opars['schemaName'],opars['queryName'],[]) print('Loaded image dataset ({} rows)'.format(len(ds['rows']))) for p in patients: pdata=odb.getPatientData(p) #dicom=pdata[p]['MainDicomTags'] #patientId=dicom['PatientID'] patientId=patientMap[p] #get all studies for pateint #qfilter={'variable':participantField,'value':patientId,'oper':'eq'} dsStudies=[row['orthancStudy'] for row in ds['rows'] if row[participantField]==patientId] #number of entries for patient (if new must be inserted) seqNum=len(dsStudies) #unique dsStudies=list(set(dsStudies)) studies=pdata['Studies'] pHex=p.split('-') print("[{:>3d}/{:>3d}] ID: {:>10s} [{}] Studies: {:>2}/{:>2} Entries in DB: {:>4d}".format(i,n,patientId,pHex[-1],len(dsStudies),len(studies),seqNum)) missingStudies=[s for s in studies if s not in dsStudies] #print('Missing studies: {}'.format(len(missingStudies))) srows=[] for s in missingStudies: sdata=odb.getStudyData(s) sdicom=sdata['MainDicomTags'] sid=sdicom['StudyInstanceUID'] #print('Data: {}'.format(sdata)) #this is try/except protetcted in previous version... sdate="19700101" try: sdate=sdicom['StudyDate'] except KeyError: pass #continue print('\tStudy[{}]: {}/{}'.format(sdate,s,sid)) for se in sdata['Series']: sedata=odb.getSeriesData(se) sedicom=sedata['MainDicomTags'] seid=sedicom['SeriesInstanceUID'] #print('Data: {}'.format(sedata)) seDesc="NONE" try: seDesc=sedicom['SeriesDescription'] except KeyError: pass #replace letters that might trip the database spanishOAcute=''.join([chr(3619),chr(3603)]) spanishAAcute=''.join([chr(3619),chr(3585)]) seDesc=re.sub(spanishOAcute,'o',seDesc) seDesc=re.sub(spanishAAcute,'a',seDesc) print('\t\tSeries[{}]: {}/{}'.format(seDesc,se,seid)) #qfilter={'variable':'orthancSeries','value':se,'oper':'eq'} #ds=db.selectRows(project,opars['schemaName'],\ # opars['queryName'],[qfilter]) #count existing entries for patient #qfilter={'variable':participantField,'value':patientId,'oper':'eq'} #ds=db.selectRows(project,opars['schemaName'],\ #opars['queryName'],[qfilter]) # seqNum=len(ds['rows']) #create new row row={} row[participantField]=patientId row['sequenceNum']=seqNum row['orthancSeries']=se row['dicomStudy']=sid row['orthancStudy']=s row['dicomSeries']=seid row['studyDate']=sdate row['seriesDescription']=seDesc srows.append(row) seqNum+=1 #upload updates if len(srows)>0: db.modifyRows('insert',project,opars['schemaName'],\ opars['queryName'],srows) i+=1 print("Done") if __name__=="__main__": main(sys.argv[1])