analysis.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. #analysis functions for cardiacSPECT dynamic data analysis
  2. #see clusterAnalysis.ipynb for details on usage
  3. import os
  4. import getData
  5. import config
  6. import subprocess
  7. import SimpleITK
  8. import numpy
  9. import re
  10. def calculateCenters(db,setup,nclass,nr):
  11. #path of scripts is ../scripts
  12. #one up
  13. baseDir=os.path.dirname(os.getcwd())
  14. #scriptPath
  15. scriptPath=os.path.join(baseDir,'scripts','generateCenters.sh')
  16. rows=getData.getPatients(db,setup)
  17. for r in rows:
  18. #download
  19. code=config.getCode(r,setup)
  20. for nc in nclass:
  21. print('centers: {}/{},{}'.format(code,nc,nr))
  22. print(subprocess.run([scriptPath,code,str(nc),str(nr)], \
  23. check=True, stdout=subprocess.PIPE).stdout)
  24. def doAnalysis(db,setup,nclass,nr,mode):
  25. baseDir=os.path.dirname(os.getcwd())#one up
  26. #this is if IVF is inferred together with k1 fits
  27. #runScript='doAnalysis.sh'
  28. #this is if IVF is taken from previous cluster fits
  29. #runScript='doAnalysisIVF.sh'
  30. if mode=='global':
  31. runScript='doAnalysis.sh'
  32. analysisType=''
  33. if mode=='IVF':
  34. runScript='doAnalysisIVF.sh'
  35. analysisType='IVF_'
  36. try:
  37. print('Setting runScript to {}'.format(runScript))
  38. except NameError:
  39. print('Mode can be one of (global,IVF))')
  40. return
  41. #either doAnalysis or doAnalysisIVF.sh
  42. scriptPath=os.path.join(baseDir,'scripts',runScript)
  43. rows=getData.getPatients(db,setup)
  44. for r in rows:
  45. dataPath=config.getLocalDir(r,setup)
  46. code=config.getCode(r,setup)
  47. for nc in nclass:
  48. for rId in numpy.arange(nr):
  49. print('{} [{} {}/{}]'.format(code,nc,rId+1,nr))
  50. #this is a duplicate of path generated in fitCenters.m
  51. fName=os.path.join(dataPath,'{}_{}_{}_{}fitParFinal.txt'.format(code,nc,rId+1,analysisType))
  52. if os.path.isfile(fName):
  53. print('Skipping; {} available.'.format(fName))
  54. continue
  55. subprocess.run([scriptPath,code,str(nc),str(rId+1)],\
  56. check=True, stdout=subprocess.PIPE)
  57. def doPixelAnalysis(db,setup,sigma2,mode='IVF'):
  58. baseDir=os.path.dirname(os.getcwd())#one up
  59. #in global mode, IVF parameters are inferred together with fits to classes
  60. #this is essentially repeat of the above, except that classes are taken as
  61. #time-response curves from pixels in the sigma2-defined neighborhood of
  62. #target pixels
  63. if mode=='global':
  64. runScript='doPixelAnalysis.sh'
  65. analysisType=''
  66. #in IVF mode, the parameters of input function are taken from the cluster fit
  67. #(doAnalysis above, with mode=general). The rest is the same as for global mode
  68. if mode=='IVF':
  69. runScript='doPixelIVFAnalysis.sh'
  70. analysisType='IVF_'
  71. #either doPixelAnalysis or doPixelAnalysisIVF.sh
  72. scriptPath=os.path.join(baseDir,'scripts',runScript)
  73. try:
  74. print('Setting runScript to {}'.format(runScript))
  75. except NameError:
  76. print('Mode can be one of (global,IVF))')
  77. return
  78. rows=getData.getPatients(db,setup)
  79. for r in rows:
  80. dataPath=config.getLocalDir(r,setup)
  81. code=config.getCode(r,setup)
  82. sName=os.path.join(dataPath,'{}_Segmentation.txt'.format(code))
  83. sFile=os.path.join(dataPath,sName)
  84. x=numpy.loadtxt(sFile)
  85. nc=x.shape[0]
  86. for s2 in sigma2:
  87. sigmaCode='{:.2f}'.format(s2)
  88. sigmaCode=re.sub('\.','p',sigmaCode)
  89. fName=os.path.join(dataPath,'{}_{}_{}_Pixel{}_fitParFinal.txt'.format(code,nc,sigmaCode,analysisType))
  90. if os.path.isfile(fName):
  91. print('Skipping; {} available.'.format(fName))
  92. continue
  93. subprocess.run([scriptPath,code,str(s2)], \
  94. check=True, stdout=subprocess.PIPE)
  95. def getIWeights(r,setup,nclass,realizationId,ic):
  96. locDir=config.getLocalDir(r,setup)
  97. code=config.getCode(r,setup)
  98. fname='{}_{}_{}_center{}_centerWeigth.nrrd'.\
  99. format(code,nclass,realizationId+1,ic+1)
  100. uFile=os.path.join(locDir,fname)
  101. imU=SimpleITK.ReadImage(uFile)
  102. return SimpleITK.GetArrayFromImage(imU)
  103. def getGaussianWeight(nU,pt,sigma2,na):
  104. #gaussian weighted summation of surrounding pixels
  105. #find point closest to the target point
  106. idx=[int(x) for x in pt]
  107. #running offset
  108. fidx=numpy.zeros(3)
  109. #half of the neighborhood
  110. na2=0.5*(na-1)
  111. fs=0
  112. fWeight=0
  113. for i in numpy.arange(na):
  114. fidx[0]=idx[0]+(i-na2)
  115. for j in numpy.arange(na):
  116. fidx[1]=idx[1]+(j-na2)
  117. for k in numpy.arange(na):
  118. fidx[2]=idx[2]+(k-na2)
  119. fidx=[int(x) for x in fidx]
  120. fd=numpy.array(fidx)-numpy.array(pt)
  121. dist2=numpy.sum(fd*fd)
  122. fw=numpy.exp(-0.5*dist2/sigma2);
  123. fs+=fw
  124. fWeight+=fw*nU[tuple(fidx)]
  125. #print('\t{}/{}: {}/{:.2f} {:.2g} {:.3g} {:.2g}'.format(idx,fidx,numpy.sqrt(dist2),fw,nU[tuple(fidx)],fs,fWeight))
  126. fWeight/=fs
  127. return fWeight
  128. #gets weights by class for a particular realization and sigma2 averaging
  129. def getWeights(db,r,setup,nclass,realizationId,sigma2,na):
  130. #for w1, classes are in 0 to nclass-1 space
  131. #na is the size of the neighborhood
  132. idFilter=config.getIdFilter(r,setup)
  133. visitFilter=config.getVisitFilter(r,setup)
  134. code=config.getCode(r,setup)
  135. rows=getData.getSegmentation(db,setup,[idFilter,visitFilter])
  136. pts={r['regionId']:[float(x) for x in [r['x'],r['y'],r['z']]] for r in rows}
  137. w={region:numpy.zeros(nclass) for region in pts}
  138. na2=0.5*(na-1)
  139. for c in numpy.arange(nclass):
  140. nU=getIWeights(r,setup,nclass,realizationId,c)
  141. for region in w:
  142. #print(pts[region])
  143. #print('{} {}/{} {}'.format(code,c,nclass,region))
  144. #gaussian weighted summation of surrounding pixels
  145. w[region][c]=getGaussianWeight(nU,pts[region],sigma2,na)
  146. return w
  147. #gets fitPar for a particular realization in [0..nr-1] range
  148. def getFitPar(r,setup,nclass,realizationId,mode=''):
  149. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  150. allowedModes=['','IVF','Pixel','PixelIVF']
  151. if mode not in allowedModes:
  152. print('Mode should be one of {}'.format(allowedModes))
  153. return []
  154. if mode=='PixelIVF':
  155. #4th parameter is sigma, not realizationId
  156. rCode='{:.2f}'.format(realizationId)
  157. rCode=re.sub('\.','p',rCode)
  158. else:
  159. #add one to match matlab 1..N array indexing
  160. rCode='{}'.format(realizationId+1)
  161. if len(mode)>0:
  162. mode=mode+'_'
  163. code=config.getCode(r,setup)
  164. fname='{}_{}_{}_{}fitParFinal.txt'.format(code,nclass,rCode,mode)
  165. locDir=config.getLocalDir(r,setup)
  166. uFile=os.path.join(locDir,fname)
  167. return numpy.genfromtxt(uFile,delimiter='\t')
  168. def getK1(fitPar,iclass):
  169. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  170. #0 to nclass-1 space
  171. return fitPar[4*iclass+5]
  172. def calculateK1(w,fitPar):
  173. #calculateK1 for region weights
  174. #return the k1 that belongs to the
  175. #maximum class in region (M) and
  176. #a weighted average (W)
  177. k1={region:{'M':0,'W':0} for region in w}
  178. for region in w:
  179. cmax=numpy.argmax(w[region])
  180. k1[region]['M']=getK1(fitPar,cmax)
  181. fs=0
  182. for c in numpy.arange(len(w[region])):
  183. fs+=w[region][c]*getK1(fitPar,c)
  184. k1[region]['W']=fs
  185. return k1
  186. def getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  187. #summary script
  188. #db is for database; needs segmentations
  189. #r,setup identify patient
  190. #nclass and nrealizations select strategy
  191. #sigma2 is for combining output from adjacent pixels
  192. #na is neighborhood size where smoothing/combination is done
  193. k1={}
  194. for rId in numpy.arange(nrealizations):
  195. w=getWeights(db,r,setup,nclass,rId,sigma2,na)
  196. fitPar=getFitPar(r,setup,nclass,rId,'IVF')
  197. qk1=calculateK1(w,fitPar)
  198. for region in w:
  199. for type in qk1[region]:
  200. try:
  201. k1[region][type].append(qk1[region][type])
  202. except KeyError:
  203. k1={region:{type:[] for type in qk1[region]} for region in w}
  204. print(type)
  205. k1[region][type].append(qk1[region][type])
  206. print('[{}] {}/{}'.format(nclass,rId+1,nrealizations))
  207. return k1
  208. def getSummaryPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  209. #summary script, same arguments as above
  210. #also return deviation over realization
  211. k1=getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na)
  212. avgType=['M','W']
  213. summaryK1={type:{region:{
  214. 'mean':numpy.mean(k1[region][type]),
  215. 'std':numpy.std(k1[region][type]),
  216. 'median':numpy.median(k1[region][type])} for region in k1}
  217. for type in avgType}
  218. return summaryK1
  219. def fullSummary(db,setup,classes,nr,sigma2,na):
  220. rows=getData.getPatients(db,setup)
  221. return \
  222. {config.getCode(r,setup):\
  223. {c:getSummaryPatientValuesByNclass(db,r,setup,c,nr,sigma2,na) for c in classes} for r in rows}
  224. def storeSummary(db,setup,summary,sigma2,na):
  225. #dsM=db.selectRows(project,'study','Imaging',[])
  226. for rCode in summary:
  227. r=config.decode(rCode,setup)
  228. idFilter=config.getIdFilter(r,setup)
  229. visitFilter=config.getVisitFilter(r,setup)
  230. for c in summary[rCode]:
  231. cFilter={'variable':'nclass','value':str(c),'oper':'eq'}
  232. for t in summary[rCode][c]:
  233. tFilter={'variable':'option','value':t,'oper':'eq'}
  234. for rId in summary[rCode][c][t]:
  235. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  236. rows=getData.getSummary(db,setup,[idFilter,visitFilter,cFilter,tFilter,rFilter])
  237. if len(rows)>0:
  238. qrow=rows[0]
  239. mode='update'
  240. else:
  241. qrow={qr:r[qr] for qr in r}
  242. qrow['nclass']=c
  243. qrow['option']=t
  244. qrow['regionId']=rId
  245. seqNum=config.getTargetSeqNum(r,setup)
  246. qrow['SequenceNum']=100+seqNum+c+0.001*rId
  247. if t=='M':
  248. qrow['SequenceNum']+=0.0001
  249. mode='insert'
  250. for v in summary[rCode][c][t][rId]:
  251. qrow[v]=summary[rCode][c][t][rId][v]
  252. qrow['sigma2']=sigma2
  253. qrow['na']=na
  254. getData.updateSummary(db,setup,mode,[qrow])
  255. def summaryPixelIVF(db,setup,sigma2):
  256. #for second type of analysis (pixel based regions)
  257. rows=getData.getPatients(db,setup)
  258. return \
  259. {config.getCode(r,setup):\
  260. {s2:getPixelIVF(db,r,setup,s2) for s2 in sigma2} for r in rows}
  261. def storeIVF(db,setup,summary):
  262. for rCode in summary:
  263. r=config.decode(rCode,setup)
  264. idFilter=config.getIdFilter(r,setup)
  265. visitFilter=config.getVisitFilter(r,setup)
  266. for s2 in summary[rCode]:
  267. sigmaFilter={'variable':'sigma2','value':str(s2),'oper':'eq'}
  268. nr=len(summary[rCode][s2])
  269. for rId in summary[rCode][s2]:
  270. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  271. typeFilter={'variable':'option','value':'D','oper':'eq'}
  272. rows=getData.getSummary(db,setup,[idFilter,visitFilter,sigmaFilter,rFilter,typeFilter])
  273. if len(rows)>0:
  274. qrow=rows[0]
  275. mode='update'
  276. else:
  277. qrow={qr:r[qr] for qr in r}
  278. qrow['sigma2']=s2
  279. qrow['regionId']=rId
  280. seqNum=config.getTargetSeqNum(r,setup)
  281. qrow['SequenceNum']=140+seqNum+0.01*rId+0.001*s2
  282. mode='insert'
  283. qrow['mean']=summary[rCode][s2][rId]
  284. qrow['na']=7
  285. qrow['nclass']=nr
  286. qrow['option']='D'
  287. getData.updateSummary(db,setup,mode,[qrow])
  288. def getPixelIVF(db,r,setup,sigma2):
  289. idFilter=config.getIdFilter(r,setup)
  290. visitFilter=config.getVisitFilter(r,setup)
  291. rows=getData.getSegmentation(db,setup,[idFilter,visitFilter])
  292. nclassIVF=len(rows)
  293. fitPar=getFitPar(r,setup,nclassIVF,sigma2,'PixelIVF')
  294. k1={r['regionId']:getK1(fitPar,r['regionId']) for r in rows}
  295. return k1