convertToNRRD.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import os
  2. import sys
  3. import json
  4. import pathlib
  5. import slicer
  6. import subprocess
  7. import shutil
  8. #for all rows in xconfig[queryName] import dicom and convert to nrrd
  9. def main(configFile):
  10. print('Running with {}'.format(configFile))
  11. with open(configFile) as f:
  12. xconfig=json.load(f)
  13. fsetup=os.path.join(os.path.expanduser('~'),'.labkey','setup.json')
  14. with open(fsetup) as f:
  15. setup=json.load(f)
  16. sys.path.append(setup['paths']['nixWrapper'])
  17. import nixWrapper
  18. nixWrapper.loadLibrary('labkeyInterface')
  19. import labkeyInterface
  20. import labkeyDatabaseBrowser
  21. import labkeyFileBrowser
  22. nixWrapper.loadLibrary('orthancInterface')
  23. import orthancInterface
  24. import orthancDatabaseBrowser
  25. import orthancFileBrowser
  26. #one should be smart to figure this out if pwd is not the same as the directory of the script
  27. pwd=os.path.dirname(os.path.abspath(__file__))
  28. pwdUp=os.path.dirname(pwd)
  29. pythonScripts=os.path.join(pwdUp,'pythonScripts')
  30. sys.path.append(pythonScripts)
  31. import config
  32. import getData
  33. print('Config loaded')
  34. net=labkeyInterface.labkeyInterface()
  35. fnet=os.path.join(os.path.expanduser('~'),'.labkey',xconfig['network'])
  36. net.init(fnet)
  37. net.getCSRF()
  38. fb=labkeyFileBrowser.labkeyFileBrowser(net)
  39. db=labkeyDatabaseBrowser.labkeyDB(net)
  40. onet=orthancInterface.orthancInterface()
  41. onet.init(fnet)
  42. ofb=orthancFileBrowser.orthancFileBrowser(onet)
  43. odb=orthancDatabaseBrowser.orthancDB(onet)
  44. qFilter=config.getFilter(xconfig)
  45. ds=db.selectRows(xconfig['project'],xconfig['schemaName'],xconfig['queryName'],qFilter)
  46. try:
  47. rows=ds['rows']
  48. except KeyError:
  49. print('No rows returned')
  50. return
  51. for r in rows:
  52. #check row sanity
  53. print("Loading {}/{}".format(config.getPatientId(r,xconfig),config.getVisitId(r,xconfig)))
  54. rPath=fb.formatPathURL(xconfig['project'],config.getOutputDir(r,xconfig))
  55. rPath+='/'+config.getNodeName(r,xconfig,'CT')+'.nrrd'
  56. entryDone=fb.entryExists(rPath)
  57. if entryDone:
  58. try:
  59. if not xconfig['recalculate']:
  60. print('Entry done')
  61. getData.updateStatus(db,r,xconfig,'convertToNRRD')
  62. continue
  63. except KeyError:
  64. print('Entry done')
  65. getData.updateStatus(db,r,xconfig,'convertToNRRD')
  66. continue
  67. print('Forced recalculation')
  68. code=config.getCode(r,xconfig)
  69. print('{} available:{}'.format(config.getNodeName(r,xconfig,'CT'),fb.entryExists(rPath)))
  70. #loadPatient into slicer
  71. patient=loadPatient(ofb,r,xconfig)
  72. if not patient:
  73. print(f'{code} failed to load from Orthanc')
  74. getData.updateStatus(db,r,xconfig,'convertToNRRD',status=False)
  75. continue
  76. #convert to nodes
  77. addCT(r,patient,xconfig)
  78. addFrames(r,patient,xconfig)
  79. addDummyInputFunction(r,patient,xconfig)
  80. nodes=slicer.mrmlScene.GetNodesByClass('vtkMRMLScalarVolumeNode')
  81. print('Nodes')
  82. for node in nodes:
  83. print('\t{}'.format(node.GetName()))
  84. storeNode(fb,r,xconfig,node)
  85. nodes=slicer.mrmlScene.GetNodesByClass('vtkMRMLDoubleArrayNode')
  86. print('Nodes (double array)')
  87. for node in nodes:
  88. print('\t{}'.format(node.GetName()))
  89. storeNode(fb,r,xconfig,node)
  90. nodes=slicer.mrmlScene.GetNodesByClass('vtkMRMLTableNode')
  91. print('Nodes (table)')
  92. for node in nodes:
  93. print('\t{}'.format(node.GetName()))
  94. storeNode(fb,r,xconfig,node)
  95. clearNodes(r,xconfig)
  96. #addCT and addFrames fill r['ct'] and r['spect']
  97. db.modifyRows('update',xconfig['project'],xconfig['schemaName'],xconfig['queryName'],[r])
  98. getData.updateStatus(db,r,xconfig,'convertToNRRD')
  99. def clearNodes(row,xconfig):
  100. nodes=slicer.mrmlScene.GetNodesByClass('vtkMRMLScalarVolumeNode')
  101. nodes1=slicer.mrmlScene.GetNodesByClass('vtkMRMLDoubleArrayNode')
  102. nodes2=slicer.mrmlScene.GetNodesByClass('vtkMRMLTableNode')
  103. for n in nodes1:
  104. nodes.AddItem(n)
  105. for n in nodes2:
  106. nodes.AddItem(n)
  107. res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
  108. def loadPatient(ofb,r,xconfig):
  109. sys.path.append('../pythonScripts')
  110. import parseDicom
  111. import vtkInterface
  112. pd=parseDicom.parseDicom()
  113. masterPath=downloadAndUnzip(ofb,r,"nmMaster",xconfig)
  114. if not masterPath:
  115. return None
  116. pd.readMasterDirectory(masterPath,False)
  117. print('Time [{} .. {}]'.format(pd.frame_start,pd.frame_stop))
  118. clearUnzipDir(r,xconfig)
  119. nmPath=downloadAndUnzip(ofb,r,"nmCorrData",xconfig)
  120. if not nmPath:
  121. return None
  122. frame_data, frame_time, frame_duration, frame_origin, \
  123. frame_pixel_size, frame_orientation=\
  124. pd.readNMDirectory(nmPath,False)
  125. print('Frame time {}'.format(frame_time))
  126. clearUnzipDir(r,xconfig)
  127. ctPath=downloadAndUnzip(ofb,r,"ct",xconfig)
  128. if not ctPath:
  129. return None
  130. ct_data,ct_origin,ct_pixel_size, \
  131. ct_orientation=pd.readCTDirectory(ctPath,False)
  132. print('CT pixel {}'.format(ct_pixel_size))
  133. clearUnzipDir(r,xconfig)
  134. ct_orientation=vtkInterface.completeOrientation(ct_orientation)
  135. frame_orientation=vtkInterface.completeOrientation(frame_orientation)
  136. ct={'data':ct_data,'origin':ct_origin,'pixel_size':ct_pixel_size,
  137. 'orientation':ct_orientation}
  138. nm={'data':frame_data,'time':frame_time,'duration':frame_duration,
  139. 'origin':frame_origin,'pixel_size':frame_pixel_size,
  140. 'orientation':frame_orientation}
  141. return {'CT':ct,'NM':nm}
  142. print('Done')
  143. def clearUnzipDir(r,xconfig):
  144. import config
  145. zipDir=config.getLocalDir(r,xconfig)
  146. try:
  147. os.mkdir(zipDir)
  148. except FileExistsError:
  149. shutil.rmtree(zipDir)
  150. return zipDir
  151. def downloadAndUnzip(ofb,r,code,xconfig):
  152. import config
  153. pathList=xconfig['tempDir'].split('/')
  154. pathList.insert(0,os.path.expanduser('~'))
  155. tempDir=os.path.join(*pathList)
  156. if not os.path.isdir(tempDir):
  157. print('Creating {}'.format(tempDir))
  158. os.makedirs(tempDir)
  159. orthancId=r[code+'OrthancId']
  160. if not orthancId:
  161. return None
  162. fileCode='{}_{}'.format(config.getCode(r,xconfig),code)
  163. zipFile=os.path.join(tempDir,fileCode+'.zip')
  164. ofb.getZip('series',orthancId,zipFile)
  165. zipDir=clearUnzipDir(r,xconfig)
  166. try:
  167. outTxt=subprocess.check_output(["unzip","-d",zipDir,"-xj",zipFile])
  168. except subprocess.CalledProcessError:
  169. print("unzip failed for {}".format(zipFile))
  170. return ""
  171. return zipDir
  172. def addCT(r,patient,xconfig):
  173. import config
  174. import vtkInterface
  175. ct=patient['CT']
  176. vtkData=vtkInterface.numpyToVTK(ct['data'],ct['data'].shape)
  177. nodeName=config.getNodeName(r,xconfig,'CT')
  178. addNode(nodeName,vtkData,ct['origin'],ct['pixel_size'],ct['orientation'],0)
  179. r['ct']=f'{nodeName}.nrrd'
  180. def addFrames(r,patient,xconfig):
  181. import vtkInterface
  182. import config
  183. #convert data from numpy.array to vtkImageData
  184. #use time point it
  185. nm=patient['NM']
  186. print("NFrames: {}".format(nm['data'].shape[3]))
  187. for it in range(0,nm['data'].shape[3]):
  188. frame_data=nm['data'][:,:,:,it];
  189. nodeName=config.getNodeName(r,xconfig,'NM',it)
  190. vtkData=vtkInterface.numpyToVTK(frame_data,frame_data.shape)
  191. addNode(nodeName,vtkData,nm['origin'],nm['pixel_size'],nm['orientation'],1)
  192. #last one will be kept
  193. r['spect']=f'{nodeName}.nrrd'
  194. def addNode(nodeName,v,origin,pixel_size,orientation,dataType):
  195. #origin,orientation in lps
  196. #dataType=0 is CT (to background)
  197. #dataType=1 is SPECT, view not adjusted, foreground,
  198. newNode=slicer.vtkMRMLScalarVolumeNode()
  199. newNode.SetName(nodeName)
  200. v.SetOrigin([0,0,0])
  201. v.SetSpacing([1,1,1])
  202. ijkToRAS = vtk.vtkMatrix4x4()
  203. #think how to do this with image orientation
  204. #orientation from lps to ras
  205. rasOrientation=[-orientation[i] if (i%3 < 2) else orientation[i]
  206. for i in range(0,len(orientation))]
  207. #origin from lps to ras
  208. rasOrigin=[-origin[i] if (i%3<2) else origin[i] for i in range(0,len(origin))]
  209. for i in range(0,3):
  210. for j in range(0,3):
  211. ijkToRAS.SetElement(i,j,pixel_size[i]*rasOrientation[3*j+i])
  212. ijkToRAS.SetElement(i,3,rasOrigin[i])
  213. newNode.SetIJKToRASMatrix(ijkToRAS)
  214. newNode.SetAndObserveImageData(v)
  215. slicer.mrmlScene.AddNode(newNode)
  216. def addDummyInputFunction(r,patient,xconfig):
  217. import config
  218. nm=patient['NM']
  219. n=nm['data'].shape[3]
  220. dnsNodeName=config.getNodeName(r,xconfig,'Dummy')
  221. dns = slicer.mrmlScene.GetNodesByClassByName('vtkMRMLDoubleArrayNode',dnsNodeName)
  222. if dns.GetNumberOfItems() == 0:
  223. try:
  224. dn = slicer.mrmlScene.AddNode(slicer.vtkMRMLDoubleArrayNode())
  225. except AttributeError:
  226. addDummyTable(dnsNodeName,n,nm)
  227. return
  228. else:
  229. dn = dns.GetItemAsObject(0)
  230. dn.SetSize(n)
  231. ft=nm['time']
  232. dt=nm['duration']
  233. for i in range(0,n):
  234. fx=ft[i]
  235. fy=dt[i]
  236. dn.SetValue(i, 0, fx)
  237. dn.SetValue(i, 1, fy)
  238. dn.SetValue(i, 2, 0)
  239. print('{} ({},{})'.format(i,fx,fy))
  240. def addDummyTable(dnsNodeName,n,nm):
  241. #add vtkMRMLTableNodes
  242. dns = slicer.mrmlScene.GetNodesByClassByName('vtkMRMLTableNode',dnsNodeName)
  243. if dns.GetNumberOfItems() == 0:
  244. dn = slicer.mrmlScene.AddNode(slicer.vtkMRMLTableNode())
  245. dn.SetName(dnsNodeName)
  246. else:
  247. dn = dns.GetItemAsObject(0)
  248. dn.RemoveAllColumns()
  249. ft=nm['time']
  250. dt=nm['duration']
  251. tCol=vtk.vtkDoubleArray()
  252. dCol=vtk.vtkDoubleArray()
  253. for i in range(n):
  254. tCol.InsertNextValue(ft[i])
  255. dCol.InsertNextValue(dt[i])
  256. tcol=dn.AddColumn(tCol)
  257. tcol.SetName('time')
  258. dcol=dn.AddColumn(dCol)
  259. dcol.SetName('duration')
  260. def storeNode(fb,row,xconfig,node):
  261. import config
  262. suffix=".nrrd"
  263. isTable=False
  264. if node.__class__.__name__=="vtkMRMLDoubleArrayNode":
  265. suffix=".mcsv"
  266. if node.__class__.__name__=="vtkMRMLTableNode":
  267. suffix=".mcsv"
  268. isTable=True
  269. if (node.__class__.__name__=="vtkMRMLTransformNode" or \
  270. node.__class__.__name__=="vtkMRMLGridTransformNode"):
  271. suffix=".h5"
  272. fileName=node.GetName()+suffix
  273. localPath=os.path.join(config.getLocalDir(row,xconfig),fileName)
  274. if isTable:
  275. storeTable(node,localPath)
  276. else:
  277. slicer.util.saveNode(node,localPath)
  278. print("Stored to: {}".format(localPath))
  279. labkeyPath=fb.buildPathURL(xconfig['project'],config.getPathList(row,xconfig))
  280. print ("Remote: {}".format(labkeyPath))
  281. #checks if exists
  282. remoteFile=labkeyPath+'/'+fileName
  283. fb.writeFileToFile(localPath,remoteFile)
  284. def storeTable(node,filename):
  285. #mimic old vtkMRMLDoubleArray format
  286. table=node.GetTable()
  287. ft=table.GetColumnByName('time')
  288. fd=table.GetColumnByName('duration')
  289. n=ft.GetNumberOfValues()
  290. print(f'Storing {n} values')
  291. with open(filename,'w') as f:
  292. f.write(f'# measurement file {filename}\n')
  293. f.write('# no labels\n')
  294. for i in range(n):
  295. _t=ft.GetTuple1(i)
  296. _d=fd.GetTuple1(i)
  297. print(f'{_t},{_d},0')
  298. f.write(f'{_t},{_d},0\n')
  299. def readPatient(fb,localDir,project,patientId):
  300. rDir=fb.formatPathURL(project,'/'.join([patientId]))
  301. lDir=os.path.join(localDir,patientId)
  302. if not os.path.isdir(lDir):
  303. os.makedirs(lDir)
  304. ok,files=fb.listRemoteDir(rDir)
  305. locFiles=[]
  306. for f in files:
  307. print(f)
  308. p=pathlib.Path(f)
  309. localFile=os.path.join(lDir,p.name)
  310. fb.readFileToFile(f,localFile)
  311. locFiles.append(localFile)
  312. print('Done')
  313. return locFiles
  314. if __name__=='__main__':
  315. main(sys.argv[1])
  316. quit()