analysis.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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(db,r,setup,mode)
  76. def doPixelAnalysisRow(db,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. sFile=segmentation.getSegmentationFileName(r,setup,db=db)
  107. segmFile=getData.getLocalPath(r,setup,sFile)
  108. if os.path.isfile(fName):
  109. print('Skipping; {} available.'.format(fName))
  110. continue
  111. runCmds=[x for x in cmds]
  112. runCmds.append('-r "path=\'{}\'; patientID=\'{}\'; sigma2=\'{}\'; segmFile=\'{}\'; {}"'.format(tempDir,code,s2,segmFile,mScript))
  113. print(f'Running with {s2}')
  114. print(subprocess.run(runCmds, check=True, stdout=subprocess.PIPE).stdout)
  115. print(f'Done')
  116. def getIWeights(r,setup,nclass,realizationId,ic):
  117. locDir=config.getLocalDir(r,setup)
  118. code=config.getCode(r,setup)
  119. fname='{}_{}_{}_center{}_centerWeigth.nrrd'.\
  120. format(code,nclass,realizationId+1,ic+1)
  121. uFile=os.path.join(locDir,fname)
  122. imU=SimpleITK.ReadImage(uFile)
  123. return SimpleITK.GetArrayFromImage(imU)
  124. def getGaussianWeight(nU,pt,sigma2,na):
  125. #gaussian weighted summation of surrounding pixels
  126. #find point closest to the target point
  127. idx=[int(x) for x in pt]
  128. #running offset
  129. fidx=numpy.zeros(3)
  130. #half of the neighborhood
  131. na2=0.5*(na-1)
  132. fs=0
  133. fWeight=0
  134. for i in numpy.arange(na):
  135. fidx[0]=idx[0]+(i-na2)
  136. for j in numpy.arange(na):
  137. fidx[1]=idx[1]+(j-na2)
  138. for k in numpy.arange(na):
  139. fidx[2]=idx[2]+(k-na2)
  140. fidx=[int(x) for x in fidx]
  141. fd=numpy.array(fidx)-numpy.array(pt)
  142. dist2=numpy.sum(fd*fd)
  143. fw=numpy.exp(-0.5*dist2/sigma2);
  144. fs+=fw
  145. fWeight+=fw*nU[tuple(fidx)]
  146. #print('\t{}/{}: {}/{:.2f} {:.2g} {:.3g} {:.2g}'.format(idx,fidx,numpy.sqrt(dist2),fw,nU[tuple(fidx)],fs,fWeight))
  147. fWeight/=fs
  148. return fWeight
  149. #gets weights by class for a particular realization and sigma2 averaging
  150. def getWeights(db,r,setup,nclass,realizationId,sigma2,na):
  151. #for w1, classes are in 0 to nclass-1 space
  152. #na is the size of the neighborhood
  153. idFilter=config.getIdFilter(r,setup)
  154. visitFilter=config.getVisitFilter(r,setup)
  155. code=config.getCode(r,setup)
  156. rows=getData.getSegmentation(db,setup,[idFilter,visitFilter])
  157. pts={r['regionId']:[float(x) for x in [r['x'],r['y'],r['z']]] for r in rows}
  158. w={region:numpy.zeros(nclass) for region in pts}
  159. na2=0.5*(na-1)
  160. for c in numpy.arange(nclass):
  161. nU=getIWeights(r,setup,nclass,realizationId,c)
  162. for region in w:
  163. #print(pts[region])
  164. #print('{} {}/{} {}'.format(code,c,nclass,region))
  165. #gaussian weighted summation of surrounding pixels
  166. w[region][c]=getGaussianWeight(nU,pts[region],sigma2,na)
  167. return w
  168. #gets fitPar for a particular realization in [0..nr-1] range
  169. def getPixelFitPar(fb,r,setup,nc,s2,mode):
  170. code=config.getCode(r,setup)
  171. f=config.getPixelFitParFinalName(code,nc,s2,mode)
  172. print('getPixelFitPar {}'.format(f))
  173. getData.copyFromServer(fb,r,setup,[f])
  174. fName=getData.getLocalPath(r,setup,f)
  175. print('getPixelFitPar {}'.format(fName))
  176. return numpy.genfromtxt(fName,delimiter='\t')
  177. def getFitPar(fb,r,setup,nclass,realizationId,mode):
  178. code=config.getCode(r,setup)
  179. f=config.getFitParFinalName(code,nclass,realizationId,mode)
  180. getData.copyFromServer(fb,r,setup,[f])
  181. fName=getData.getLocalPath(r,setup,f)
  182. return numpy.genfromtxt(fName,delimiter='\t')
  183. def getFitParBackup(r,setup,nclass,realizationId,mode=''):
  184. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  185. allowedModes=['','IVF','Pixel','PixelIVF']
  186. if mode not in allowedModes:
  187. print('Mode should be one of {}'.format(allowedModes))
  188. return []
  189. if mode=='PixelIVF':
  190. #4th parameter is sigma, not realizationId
  191. rCode='{:.2f}'.format(realizationId)
  192. rCode=re.sub('\.','p',rCode)
  193. else:
  194. #add one to match matlab 1..N array indexing
  195. rCode='{}'.format(realizationId+1)
  196. if len(mode)>0:
  197. mode=mode+'_'
  198. code=config.getCode(r,setup)
  199. fname='{}_{}_{}_{}fitParFinal.txt'.format(code,nclass,rCode,mode)
  200. locDir=config.getLocalDir(r,setup)
  201. uFile=os.path.join(locDir,fname)
  202. return numpy.genfromtxt(uFile,delimiter='\t')
  203. def getK1(fitPar,iclass):
  204. #fitGoodnes A tau alpha delay [k1 BVF k2 delay]xNcenters
  205. #0 to nclass-1 space
  206. return fitPar[4*iclass+5]
  207. def calculateK1(w,fitPar):
  208. #calculateK1 for region weights
  209. #return the k1 that belongs to the
  210. #maximum class in region (M) and
  211. #a weighted average (W)
  212. k1={region:{'M':0,'W':0} for region in w}
  213. for region in w:
  214. cmax=numpy.argmax(w[region])
  215. k1[region]['M']=getK1(fitPar,cmax)
  216. fs=0
  217. for c in numpy.arange(len(w[region])):
  218. fs+=w[region][c]*getK1(fitPar,c)
  219. k1[region]['W']=fs
  220. return k1
  221. def getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  222. #summary script
  223. #db is for database; needs segmentations
  224. #r,setup identify patient
  225. #nclass and nrealizations select strategy
  226. #sigma2 is for combining output from adjacent pixels
  227. #na is neighborhood size where smoothing/combination is done
  228. k1={}
  229. for rId in numpy.arange(nrealizations):
  230. w=getWeights(db,r,setup,nclass,rId,sigma2,na)
  231. fitPar=getFitPar(r,setup,nclass,rId,'IVF')
  232. qk1=calculateK1(w,fitPar)
  233. for region in w:
  234. for type in qk1[region]:
  235. try:
  236. k1[region][type].append(qk1[region][type])
  237. except KeyError:
  238. k1={region:{type:[] for type in qk1[region]} for region in w}
  239. print(type)
  240. k1[region][type].append(qk1[region][type])
  241. print('[{}] {}/{}'.format(nclass,rId+1,nrealizations))
  242. return k1
  243. def getSummaryPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na):
  244. #summary script, same arguments as above
  245. #also return deviation over realization
  246. k1=getPatientValuesByNclass(db,r,setup,nclass,nrealizations,sigma2,na)
  247. avgType=['M','W']
  248. summaryK1={type:{region:{
  249. 'mean':numpy.mean(k1[region][type]),
  250. 'std':numpy.std(k1[region][type]),
  251. 'median':numpy.median(k1[region][type])} for region in k1}
  252. for type in avgType}
  253. return summaryK1
  254. def fullSummary(db,setup,classes,nr,sigma2,na):
  255. rows=getData.getPatients(db,setup)
  256. return \
  257. {config.getCode(r,setup):\
  258. {c:getSummaryPatientValuesByNclass(db,r,setup,c,nr,sigma2,na) for c in classes} for r in rows}
  259. def storeSummary(db,setup,summary,sigma2,na):
  260. #dsM=db.selectRows(project,'study','Imaging',[])
  261. for rCode in summary:
  262. r=config.decode(rCode,setup)
  263. idFilter=config.getIdFilter(r,setup)
  264. visitFilter=config.getVisitFilter(r,setup)
  265. for c in summary[rCode]:
  266. cFilter={'variable':'nclass','value':str(c),'oper':'eq'}
  267. for t in summary[rCode][c]:
  268. tFilter={'variable':'option','value':t,'oper':'eq'}
  269. for rId in summary[rCode][c][t]:
  270. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  271. rows=getData.getSummary(db,setup,[idFilter,visitFilter,cFilter,tFilter,rFilter])
  272. if len(rows)>0:
  273. qrow=rows[0]
  274. mode='update'
  275. else:
  276. qrow={qr:r[qr] for qr in r}
  277. qrow['nclass']=c
  278. qrow['option']=t
  279. qrow['regionId']=rId
  280. seqNum=config.getTargetSeqNum(r,setup)
  281. qrow['SequenceNum']=100+seqNum+c+0.001*rId
  282. if t=='M':
  283. qrow['SequenceNum']+=0.0001
  284. mode='insert'
  285. for v in summary[rCode][c][t][rId]:
  286. qrow[v]=summary[rCode][c][t][rId][v]
  287. qrow['sigma2']=sigma2
  288. qrow['na']=na
  289. getData.updateSummary(db,setup,mode,[qrow])
  290. def summaryPixelIVF(db,fb,setup):
  291. #for second type of analysis (pixel based regions)
  292. qfilter=config.getFilter(setup)
  293. rows=getData.getPatients(db,setup,qfilter)
  294. sigma2=setup['sigma2']
  295. return \
  296. {config.getCode(r,setup):\
  297. {s2:getPixelIVF(db,fb,r,setup,s2) for s2 in sigma2} for r in rows}
  298. def storeIVF(db,setup,summary):
  299. for rCode in summary:
  300. r=config.decode(rCode,setup)
  301. idFilter=config.getIdFilter(r,setup)
  302. visitFilter=config.getVisitFilter(r,setup)
  303. for s2 in summary[rCode]:
  304. sigmaFilter={'variable':'sigma2','value':str(s2),'oper':'eq'}
  305. nr=len(summary[rCode][s2])
  306. for rId in summary[rCode][s2]:
  307. rFilter={'variable':'regionId','value':str(rId),'oper':'eq'}
  308. typeFilter={'variable':'option','value':'D','oper':'eq'}
  309. rows=getData.getSummary(db,setup,[idFilter,visitFilter,sigmaFilter,rFilter,typeFilter])
  310. if len(rows)>0:
  311. qrow=rows[0]
  312. mode='update'
  313. else:
  314. qrow={qr:r[qr] for qr in r}
  315. qrow['sigma2']=s2
  316. qrow['regionId']=rId
  317. seqNum=config.getTargetSeqNum(r,setup)
  318. qrow['SequenceNum']=140+seqNum+0.01*rId+0.001*s2
  319. mode='insert'
  320. qrow['mean']=summary[rCode][s2][rId]
  321. qrow['na']=7
  322. qrow['nclass']=nr
  323. qrow['option']='D'
  324. getData.updateSummary(db,setup,mode,[qrow])
  325. def getPixelIVF(db,fb,r,setup,sigma2):
  326. idFilter=config.getIdFilter(r,setup)
  327. visitFilter=config.getVisitFilter(r,setup)
  328. nclassIVF=segmentation.getNC(r,setup)
  329. regions=[{'regionId':x+1} for x in range(nclassIVF)]
  330. #rows=getData.getSegmentation(db,setup,[idFilter,visitFilter])
  331. #nclassIVF=len(rows)
  332. #x=segmentation.loadSegmentation(db,fb,r,setup)
  333. #nclassIVF=x.shape[0]
  334. #this assumes segmentation is loaded
  335. #nclassIVF=segmentation.getNC(r,setup)
  336. #fitPar=getFitPar(r,setup,nclassIVF,sigma2,'PixelIVF')
  337. fitPar=getPixelFitPar(fb,r,setup,nclassIVF,sigma2,'IVF')
  338. print(fitPar)
  339. k1={r['regionId']:getK1(fitPar,r['regionId']-1) for r in regions}
  340. print(k1)
  341. return k1