analysis.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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. print('getPixelFitPar {}'.format(f))
  169. getData.copyFromServer(fb,r,setup,[f])
  170. fName=getData.getLocalPath(r,setup,f)
  171. print('getPixelFitPar {}'.format(fName))
  172. return numpy.genfromtxt(fName,delimiter='\t')
  173. def getFitPar(fb,r,setup,nclass,realizationId,mode):
  174. code=config.getCode(r,setup)
  175. f=config.getFitParFinalName(code,nclass,realizationId,mode)
  176. getData.copyFromServer(fb,r,setup,[f])
  177. fName=getData.getLocalPath(r,setup,f)
  178. return numpy.genfromtxt(fName,delimiter='\t')
  179. def getFitParBackup(r,setup,nclass,realizationId,mode=''):
  180. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  181. allowedModes=['','IVF','Pixel','PixelIVF']
  182. if mode not in allowedModes:
  183. print('Mode should be one of {}'.format(allowedModes))
  184. return []
  185. if mode=='PixelIVF':
  186. #4th parameter is sigma, not realizationId
  187. rCode='{:.2f}'.format(realizationId)
  188. rCode=re.sub('\.','p',rCode)
  189. else:
  190. #add one to match matlab 1..N array indexing
  191. rCode='{}'.format(realizationId+1)
  192. if len(mode)>0:
  193. mode=mode+'_'
  194. code=config.getCode(r,setup)
  195. fname='{}_{}_{}_{}fitParFinal.txt'.format(code,nclass,rCode,mode)
  196. locDir=config.getLocalDir(r,setup)
  197. uFile=os.path.join(locDir,fname)
  198. return numpy.genfromtxt(uFile,delimiter='\t')
  199. def getK1(fitPar,iclass):
  200. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  201. #0 to nclass-1 space
  202. return fitPar[4*iclass+5]
  203. def calculateK1(w,fitPar):
  204. #calculateK1 for region weights
  205. #return the k1 that belongs to the
  206. #maximum class in region (M) and
  207. #a weighted average (W)
  208. k1={region:{'M':0,'W':0} for region in w}
  209. for region in w:
  210. cmax=numpy.argmax(w[region])
  211. k1[region]['M']=getK1(fitPar,cmax)
  212. fs=0
  213. for c in numpy.arange(len(w[region])):
  214. fs+=w[region][c]*getK1(fitPar,c)
  215. k1[region]['W']=fs
  216. return k1
  217. def getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  218. #summary script
  219. #db is for database; needs segmentations
  220. #r,setup identify patient
  221. #nclass and nrealizations select strategy
  222. #sigma2 is for combining output from adjacent pixels
  223. #na is neighborhood size where smoothing/combination is done
  224. k1={}
  225. for rId in numpy.arange(nrealizations):
  226. w=getWeights(db,r,setup,nclass,rId,sigma2,na)
  227. fitPar=getFitPar(r,setup,nclass,rId,'IVF')
  228. qk1=calculateK1(w,fitPar)
  229. for region in w:
  230. for type in qk1[region]:
  231. try:
  232. k1[region][type].append(qk1[region][type])
  233. except KeyError:
  234. k1={region:{type:[] for type in qk1[region]} for region in w}
  235. print(type)
  236. k1[region][type].append(qk1[region][type])
  237. print('[{}] {}/{}'.format(nclass,rId+1,nrealizations))
  238. return k1
  239. def getSummaryPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  240. #summary script, same arguments as above
  241. #also return deviation over realization
  242. k1=getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na)
  243. avgType=['M','W']
  244. summaryK1={type:{region:{
  245. 'mean':numpy.mean(k1[region][type]),
  246. 'std':numpy.std(k1[region][type]),
  247. 'median':numpy.median(k1[region][type])} for region in k1}
  248. for type in avgType}
  249. return summaryK1
  250. def fullSummary(db,setup,classes,nr,sigma2,na):
  251. rows=getData.getPatients(db,setup)
  252. return \
  253. {config.getCode(r,setup):\
  254. {c:getSummaryPatientValuesByNclass(db,r,setup,c,nr,sigma2,na) for c in classes} for r in rows}
  255. def storeSummary(db,setup,summary,sigma2,na):
  256. #dsM=db.selectRows(project,'study','Imaging',[])
  257. for rCode in summary:
  258. r=config.decode(rCode,setup)
  259. idFilter=config.getIdFilter(r,setup)
  260. visitFilter=config.getVisitFilter(r,setup)
  261. for c in summary[rCode]:
  262. cFilter={'variable':'nclass','value':str(c),'oper':'eq'}
  263. for t in summary[rCode][c]:
  264. tFilter={'variable':'option','value':t,'oper':'eq'}
  265. for rId in summary[rCode][c][t]:
  266. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  267. rows=getData.getSummary(db,setup,[idFilter,visitFilter,cFilter,tFilter,rFilter])
  268. if len(rows)>0:
  269. qrow=rows[0]
  270. mode='update'
  271. else:
  272. qrow={qr:r[qr] for qr in r}
  273. qrow['nclass']=c
  274. qrow['option']=t
  275. qrow['regionId']=rId
  276. seqNum=config.getTargetSeqNum(r,setup)
  277. qrow['SequenceNum']=100+seqNum+c+0.001*rId
  278. if t=='M':
  279. qrow['SequenceNum']+=0.0001
  280. mode='insert'
  281. for v in summary[rCode][c][t][rId]:
  282. qrow[v]=summary[rCode][c][t][rId][v]
  283. qrow['sigma2']=sigma2
  284. qrow['na']=na
  285. getData.updateSummary(db,setup,mode,[qrow])
  286. def summaryPixelIVF(db,fb,setup):
  287. #for second type of analysis (pixel based regions)
  288. qfilter=config.getFilter(setup)
  289. rows=getData.getPatients(db,setup,qfilter)
  290. sigma2=setup['sigma2']
  291. return \
  292. {config.getCode(r,setup):\
  293. {s2:getPixelIVF(db,fb,r,setup,s2) for s2 in sigma2} for r in rows}
  294. def storeIVF(db,setup,summary):
  295. for rCode in summary:
  296. r=config.decode(rCode,setup)
  297. idFilter=config.getIdFilter(r,setup)
  298. visitFilter=config.getVisitFilter(r,setup)
  299. for s2 in summary[rCode]:
  300. sigmaFilter={'variable':'sigma2','value':str(s2),'oper':'eq'}
  301. nr=len(summary[rCode][s2])
  302. for rId in summary[rCode][s2]:
  303. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  304. typeFilter={'variable':'option','value':'D','oper':'eq'}
  305. rows=getData.getSummary(db,setup,[idFilter,visitFilter,sigmaFilter,rFilter,typeFilter])
  306. if len(rows)>0:
  307. qrow=rows[0]
  308. mode='update'
  309. else:
  310. qrow={qr:r[qr] for qr in r}
  311. qrow['sigma2']=s2
  312. qrow['regionId']=rId
  313. seqNum=config.getTargetSeqNum(r,setup)
  314. qrow['SequenceNum']=140+seqNum+0.01*rId+0.001*s2
  315. mode='insert'
  316. qrow['mean']=summary[rCode][s2][rId]
  317. qrow['na']=7
  318. qrow['nclass']=nr
  319. qrow['option']='D'
  320. getData.updateSummary(db,setup,mode,[qrow])
  321. def getPixelIVF(db,fb,r,setup,sigma2):
  322. idFilter=config.getIdFilter(r,setup)
  323. visitFilter=config.getVisitFilter(r,setup)
  324. rows=getData.getSegmentation(db,setup,[idFilter,visitFilter])
  325. nclassIVF=len(rows)
  326. #x=segmentation.loadSegmentation(db,fb,r,setup)
  327. #nclassIVF=x.shape[0]
  328. #this assumes segmentation is loaded
  329. #nclassIVF=segmentation.getNC(r,setup)
  330. #fitPar=getFitPar(r,setup,nclassIVF,sigma2,'PixelIVF')
  331. fitPar=getPixelFitPar(fb,r,setup,nclassIVF,sigma2,'IVF')
  332. print(fitPar)
  333. k1={r['regionId']:getK1(fitPar,r['regionId']) for r in rows}
  334. return k1