analysis.py 13 KB

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