runStat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import statUtils
  2. import os
  3. import radiomics
  4. import SimpleITK
  5. import sys
  6. import json
  7. import numpy
  8. def main(parFile='../templates/statistics.json'):
  9. featureExtractor=radiomics.featureextractor.RadiomicsFeatureExtractor
  10. setup=statUtils.loadSetup(parFile)
  11. rFile='radiomics.json'
  12. forceLiver=setup.get('forceLiver',False)
  13. forceSUVMax=setup.get('forceSUVMax',False)
  14. forceLiver15=setup.get('forceLiver15',False)
  15. forceGlobal=setup.get('forceGlobal',False)
  16. doUpload=setup.get('doUpload',True)
  17. #update threshold values if needed
  18. with open(rFile,'w') as f:
  19. f.write(json.dumps(setup['radiomics']))
  20. setup['db'],setup['fb']=statUtils.connectDB('onko-nix')
  21. users=statUtils.getUsers(setup['db'],setup['project'])
  22. p=setup['project']
  23. qFilter=[]
  24. try:
  25. vList=';'.join(setup['participants'])
  26. qFilter.append({'variable':'ParticipantId','value':vList,'oper':'in'})
  27. except KeyError:
  28. pass
  29. try:
  30. vList=';'.join(setup['visits'])
  31. qFilter.append({'variable':'visitCode','value':vList,'oper':'in'})
  32. except KeyError:
  33. pass
  34. ds=setup['db'].selectRows(p,'study',setup['imagingDataset'],qFilter)
  35. if not os.path.isdir(setup['localDir']):
  36. os.mkdir(setup['localDir'])
  37. #select just the first row; debugging
  38. rows=ds['rows']
  39. setup['values']=['COM','MTV','TLG','SUVmean','SUVmax','voxelCount','SUVSD']
  40. setup['featureExtractor']=featureExtractor(rFile)
  41. #make sure we calculate Variance
  42. firstOrder=setup['radiomics']['featureClass']['firstorder']
  43. if 'Variance' not in firstOrder:
  44. firstOrder.append('Variance')
  45. n=setup.get('n',-1)
  46. if n>0:
  47. rows=rows[:n]
  48. for r in rows:
  49. setup['SUVdataset']='SUVanalysis'
  50. globalDone=not forceGlobal and checkData(setup,r)
  51. #check if we have to do calculation
  52. setup['SUVdataset']='SUVanalysis_liver'
  53. liverDone=not forceLiver and checkData(setup,r)
  54. setup['SUVdataset']='SUVanalysis_SUVmax'
  55. suvMaxDone=not forceSUVMax and checkData(setup,r)
  56. setup['SUVdataset']='SUVanalysis_liver1p5'
  57. liver1p5Done=not forceLiver15 and checkData(setup,r)
  58. doneCode=f'({globalDone}/{liverDone}/{liver1p5Done}/{suvMaxDone})'
  59. print(f'Done: (global/liver/liver1p5/suvMax): {doneCode}')
  60. if globalDone and liverDone and suvMaxDone and liver1p5Done:
  61. print('Skipping {} {}'.format(r['ParticipantId'],r['visitCode']))
  62. continue
  63. #PET
  64. for q in ['petResampled']:
  65. localPath=statUtils.getImage(setup,r,q)
  66. if localPath=="NONE":
  67. continue
  68. pet=SimpleITK.ReadImage(localPath)
  69. #Seg
  70. segPaths=statUtils.getSegmentations(setup,r)
  71. if "NONE" in segPaths.values():
  72. os.remove(localPath)
  73. continue
  74. segKeys=list(segPaths.keys())
  75. for x in segPaths:
  76. print('Loaded {}/{}'.format(users[x],segPaths[x]))
  77. seg={x:SimpleITK.ReadImage(segPaths[x]) for x in segPaths}
  78. globalThreshold={x:0 for x in seg}
  79. if not globalDone:
  80. #get value for maximum in organs or liver mean and std
  81. setup['SUVdataset']='SUVanalysis'
  82. globalOutputs=thresholdAnalysis(setup,r,pet,seg,globalThreshold)
  83. if doUpload:
  84. uploadData(setup,r,globalOutputs)
  85. default={'SUVmax':0,'SUVmean':0,'SUVSD':0}
  86. setup['SUVdataset']='SUVanalysis'
  87. outputs=loadGlobals(setup,r,seg)
  88. #liver threshold
  89. liverId=1
  90. liverThreshold={x:outputs[x].get(liverId,default)['SUVmean']
  91. +2*outputs[x].get(liverId,default)['SUVSD']
  92. for x in outputs}
  93. liver1p5Threshold={x:1.5*outputs[x].get(liverId,default)['SUVmean']
  94. +2*outputs[x].get(liverId,default)['SUVSD']
  95. for x in outputs}
  96. lesionId=4
  97. bmId=3
  98. suvMax={x:numpy.max([outputs[x].get(lesionId,default)['SUVmax'],
  99. outputs[x].get(bmId,default)['SUVmax']])
  100. for x in outputs}
  101. suvMaxThreshold={x:0.41*suvMax[x] for x in suvMax}
  102. ftStr='thr[liver]={} thr[liver/1.5]={} thr(suvmax)={}'
  103. print(ftStr.format(liverThreshold,liver1p5Threshold,suvMaxThreshold))
  104. if not liverDone:
  105. setup['SUVdataset']='SUVanalysis_liver'
  106. liverOutputs=thresholdAnalysis(setup,r,pet,seg,liverThreshold)
  107. if doUpload:
  108. uploadData(setup,r,liverOutputs)
  109. #also for threshold=1.5
  110. if not liver1p5Done:
  111. setup['SUVdataset']='SUVanalysis_liver1p5'
  112. liver1p5Outputs=thresholdAnalysis(setup,r,pet,seg,liver1p5Threshold)
  113. if doUpload:
  114. uploadData(setup,r,liver1p5Outputs)
  115. if not suvMaxDone:
  116. setup['SUVdataset']='SUVanalysis_SUVmax'
  117. suvMaxOutputs=thresholdAnalysis(setup,r,pet,seg,suvMaxThreshold)
  118. if doUpload:
  119. uploadData(setup,r,suvMaxOutputs)
  120. #skip threshold of 4
  121. doThreshold4=False
  122. if doThreshold4:
  123. #threshold of 4
  124. setup['radiomics']['setting']['resegmentRange']=[4]
  125. setup['radiomics']['setting']['resegmentShape']=True
  126. outputs4=getValues(setup,r,pet,seg)
  127. setup['SUVdataset']='SUVanalysis_SUV4'
  128. uploadData(setup,r,outputs4)
  129. #cleanup
  130. os.remove(localPath)
  131. for x in segPaths:
  132. os.remove(segPaths[x])
  133. def loadGlobals(setup,r,seg):
  134. matchingVisits={
  135. 'VISIT_1':['VISIT_1','VISIT_11'],
  136. 'VISIT_11':['VISIT_1','VISIT_11'],
  137. 'VISIT_2':['VISIT_2','VISIT_12'],
  138. 'VISIT_12':['VISIT_2','VISIT_12'],
  139. 'VISIT_3':['VISIT_3','VISIT_13'],
  140. 'VISIT_13':['VISIT_3','VISIT_13'],
  141. 'VISIT_4':['VISIT_4','VISIT_14'],
  142. 'VISIT_14':['VISIT_4','VISIT_14']}
  143. ids=[1,2,3,4,5,6]
  144. p=setup['project']
  145. d=setup['SUVdataset']
  146. idVar='ParticipantId'
  147. vCodes=';'.join(matchingVisits[r['visitCode']])
  148. vars=['MTV','TLG','SUVmean','SUVmax','SUVSD']
  149. outputs={}
  150. for x in seg:
  151. outputs[x]={}
  152. for i in ids:
  153. qFilter=[]
  154. qFilter.append({'variable':idVar,'value':r[idVar],'oper':'eq'})
  155. qFilter.append({'variable':'visitCode','value':vCodes,'oper':'in'})
  156. qFilter.append({'variable':'User','value':f'{x}','oper':'eq'})
  157. qFilter.append({'variable':'segment','value':f'{i}','oper':'eq'})
  158. ds=setup['db'].selectRows(p,'study',d,qFilter)
  159. outputs[x][i]={v:combineData(ds['rows'],v) for v in vars}
  160. return outputs
  161. def combineData(rows,x):
  162. dt=numpy.array([float(r[x] or 0) for r in rows])
  163. #print('Combine data[{}] {}'.format(x,dt))
  164. r=0
  165. if x=='MTV' or x=='TLG':
  166. r=numpy.sum(dt)
  167. if x=='SUVmean':
  168. m=combineData(rows,'MTV')
  169. if m==0:
  170. r=0
  171. else:
  172. r=combineData(rows,'TLG')/m
  173. if x=='SUVmax':
  174. try:
  175. r=numpy.max(dt)
  176. except ValueError:
  177. #empty array
  178. r=0
  179. if x=='SUVSD':
  180. #this should be probably be done right,
  181. #here I only want it to work also if one of the components is 0
  182. r=numpy.sqrt(numpy.sum(dt*dt))
  183. #print(f'Result: {r}')
  184. return r
  185. def thresholdAnalysis(setup,r,pet,seg,thrs):
  186. #thresholds thrs are by segmentation author
  187. outputs={}
  188. for s in thrs:
  189. t=thrs[s]
  190. print(f'Thr[{s}]: {t}')
  191. #update radiomics setting
  192. setup['radiomics']['setting']['resegmentRange']=[thrs[s]]
  193. setup['radiomics']['setting']['resegmentShape']=True
  194. outputs[s]=getValuesForSegmentation(setup,r,pet,seg[s])
  195. _=[outputs[s][y].update({'threshold':thrs[s]}) for y in outputs[s]]
  196. print(outputs)
  197. return outputs
  198. def getValues(setup,row,pet,seg):
  199. #seg is a list of segments
  200. rFile='radiomics.json'
  201. #short function names
  202. featureExtractor=radiomics.featureextractor.RadiomicsFeatureExtractor
  203. getStats=statUtils.getRadiomicsComponentStats
  204. with open(rFile,'w') as f:
  205. f.write(json.dumps(setup['radiomics']))
  206. setup['featureExtractor']=featureExtractor(rFile)
  207. #find labels associated with each (non-overlaping) segmentation
  208. ids=statUtils.getSegments(list(seg.values())[0])
  209. outputs={x:{} for x in seg}
  210. for x in seg:
  211. for id in ids:
  212. print('{} {}'.format(id,ids[id]))
  213. try:
  214. output=getStats(setup,pet,seg[x],ids[id])
  215. except ValueError:
  216. continue
  217. outputs[x][ids[id]]=output
  218. os.remove(rFile)
  219. return outputs
  220. def getValuesForSegmentation(setup,row,pet,seg):
  221. #short function names
  222. featureExtractor=radiomics.featureextractor.RadiomicsFeatureExtractor
  223. getStats=statUtils.getRadiomicsComponentStats
  224. rFile='radiomics.json'
  225. with open(rFile,'w') as f:
  226. f.write(json.dumps(setup['radiomics']))
  227. setup['featureExtractor']=featureExtractor(rFile)
  228. #find labels associated with each (non-overlaping) segmentation
  229. ids=statUtils.getSegments(seg)
  230. outputs={}
  231. for id in ids:
  232. print('{} {}'.format(id,ids[id]))
  233. try:
  234. output=getStats(setup,pet,seg,ids[id])
  235. except ValueError:
  236. continue
  237. outputs[ids[id]]=output
  238. os.remove(rFile)
  239. return outputs
  240. def uploadData(setup,r,outputs):
  241. baseVar=['ParticipantId','SequenceNum','patientCode','visitCode']
  242. p=setup['project']
  243. d=setup['SUVdataset']
  244. for x in outputs:
  245. for s in outputs[x]:
  246. output=outputs[x][s]
  247. output.update({x:r[x] for x in baseVar})
  248. output['User']=x
  249. output['segment']=s
  250. statUtils.updateDatasetRows(setup['db'],p,d,[output])
  251. def checkData(setup,r):
  252. p=setup['project']
  253. d=setup['SUVdataset']
  254. qFilter=[]
  255. idVar='ParticipantId'
  256. qFilter.append({'variable':idVar,'value':r[idVar],'oper':'eq'})
  257. qFilter.append({'variable':'visitCode','value':r['visitCode'],'oper':'eq'})
  258. ds=setup['db'].selectRows(p,'study',d,qFilter)
  259. n=len(ds['rows'])
  260. print('[{}:{}/{}] got {} rows.'.format(d,r[idVar],r['visitCode'],n))
  261. return n>0
  262. if __name__=='__main__':
  263. main(sys.argv[1])