analysis.py 14 KB

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