|  | @@ -0,0 +1,847 @@
 | 
	
		
			
				|  |  | +import os
 | 
	
		
			
				|  |  | +import unittest
 | 
	
		
			
				|  |  | +from __main__ import vtk, qt, ctk, slicer
 | 
	
		
			
				|  |  | +from slicer.ScriptedLoadableModule import *
 | 
	
		
			
				|  |  | +import json
 | 
	
		
			
				|  |  | +import datetime
 | 
	
		
			
				|  |  | +import sys
 | 
	
		
			
				|  |  | +import nixModule
 | 
	
		
			
				|  |  | +import pathlib
 | 
	
		
			
				|  |  | +import chardet
 | 
	
		
			
				|  |  | +import re
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +# labkeySlicerPythonExtension
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class imageBrowser(ScriptedLoadableModule):
 | 
	
		
			
				|  |  | +    """Uses ScriptedLoadableModule base class, available at:
 | 
	
		
			
				|  |  | +      https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    def __init__(self, parent):
 | 
	
		
			
				|  |  | +        ScriptedLoadableModule.__init__(self, parent)
 | 
	
		
			
				|  |  | +        self.parent.title = "image Browser" 
 | 
	
		
			
				|  |  | +        # TODO make this more human readable by adding spaces
 | 
	
		
			
				|  |  | +        self.parent.categories = ["LabKey"]
 | 
	
		
			
				|  |  | +        self.parent.dependencies = []
 | 
	
		
			
				|  |  | +        self.parent.contributors = ["Andrej Studen (UL/FMF)"] 
 | 
	
		
			
				|  |  | +        # replace with "Firstname Lastname (Organization)"
 | 
	
		
			
				|  |  | +        self.parent.helpText = """
 | 
	
		
			
				|  |  | +        Interface to irAEMM files in LabKey
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        self.parent.acknowledgementText = """
 | 
	
		
			
				|  |  | +        Developed within the medical physics research programme 
 | 
	
		
			
				|  |  | +        of the Slovenian research agency.
 | 
	
		
			
				|  |  | +        """ # replace with organization, grant and thanks.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +# labkeySlicerPythonExtensionWidget
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class imageBrowserWidget(ScriptedLoadableModuleWidget):
 | 
	
		
			
				|  |  | +    """Uses ScriptedLoadableModuleWidget base class, available at:
 | 
	
		
			
				|  |  | +  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setup(self):
 | 
	
		
			
				|  |  | +        print("Setting up imageBrowserWidget")
 | 
	
		
			
				|  |  | +        ScriptedLoadableModuleWidget.setup(self)
 | 
	
		
			
				|  |  | +        # Instantiate and connect widgets ...
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +        self.logic=imageBrowserLogic(self)
 | 
	
		
			
				|  |  | +        self.addInfoSection()
 | 
	
		
			
				|  |  | +        self.addSetupSection()
 | 
	
		
			
				|  |  | +        self.addPatientsSelector()
 | 
	
		
			
				|  |  | +        self.addSegmentEditor()
 | 
	
		
			
				|  |  | +        self.addWindowManipulator()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def addInfoSection(self):  
 | 
	
		
			
				|  |  | +        #a python overview of json settings
 | 
	
		
			
				|  |  | +        infoCollapsibleButton = ctk.ctkCollapsibleButton()
 | 
	
		
			
				|  |  | +        infoCollapsibleButton.text = "Info"
 | 
	
		
			
				|  |  | +        self.layout.addWidget(infoCollapsibleButton)
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        infoLayout = qt.QFormLayout(infoCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.participantField=qt.QLabel("PatientId")
 | 
	
		
			
				|  |  | +        infoLayout.addRow("Participant field:",self.participantField)
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        self.ctField=qt.QLabel("ctResampled")
 | 
	
		
			
				|  |  | +        infoLayout.addRow("Data field (CT):",self.ctField)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.spectField=qt.QLabel("spectResampled")
 | 
	
		
			
				|  |  | +        infoLayout.addRow("Data field (PET):",self.spectField)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.userField=qt.QLabel("Loading")
 | 
	
		
			
				|  |  | +        infoLayout.addRow("User",self.userField)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.idField=qt.QLabel("Loading")
 | 
	
		
			
				|  |  | +        infoLayout.addRow("ID",self.idField)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #Add logic at some point
 | 
	
		
			
				|  |  | +        #self.logic=imageBrowserLogic(self)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def addPatientsSelector(self):
 | 
	
		
			
				|  |  | +        #
 | 
	
		
			
				|  |  | +        # Patients Area
 | 
	
		
			
				|  |  | +        #
 | 
	
		
			
				|  |  | +        patientsCollapsibleButton = ctk.ctkCollapsibleButton()
 | 
	
		
			
				|  |  | +        patientsCollapsibleButton.text = "Patients"
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        #don't add it yet
 | 
	
		
			
				|  |  | +        self.layout.addWidget(patientsCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        patientsFormLayout = qt.QFormLayout(patientsCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.patientList=qt.QComboBox()
 | 
	
		
			
				|  |  | +        self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
 | 
	
		
			
				|  |  | +        self.patientList.setEditable(True)
 | 
	
		
			
				|  |  | +        self.patientList.setInsertPolicy(qt.QComboBox.NoInsert)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Patient:",self.patientList)
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.visitList=qt.QComboBox()
 | 
	
		
			
				|  |  | +        self.visitList.currentIndexChanged.connect(self.onVisitListChanged)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Visit:",self.visitList)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.ctCode=qt.QLabel("ctCode")
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("CT:",self.ctCode)
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        self.petCode=qt.QLabel("petCode")
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("PET:",self.petCode)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.patientLoad=qt.QPushButton("Load")
 | 
	
		
			
				|  |  | +        self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Load patient",self.patientLoad)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.patientSave=qt.QPushButton("Save")
 | 
	
		
			
				|  |  | +        self.patientSave.clicked.connect(self.onPatientSaveButtonClicked)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Save segmentation",self.patientSave)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.patientClear=qt.QPushButton("Clear")
 | 
	
		
			
				|  |  | +        self.patientClear.clicked.connect(self.onPatientClearButtonClicked)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Clear patient",self.patientClear)
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        self.keepCached=qt.QCheckBox("keep Cached")
 | 
	
		
			
				|  |  | +        self.keepCached.setChecked(1)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Keep cached",self.keepCached)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.forceReload=qt.QCheckBox("Force reload")
 | 
	
		
			
				|  |  | +        self.forceReload.setChecked(0)
 | 
	
		
			
				|  |  | +        patientsFormLayout.addRow("Force reload",self.forceReload)
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def addSetupSection(self):
 | 
	
		
			
				|  |  | +        setupCollapsibleButton = ctk.ctkCollapsibleButton()
 | 
	
		
			
				|  |  | +        setupCollapsibleButton.text = "Setup"
 | 
	
		
			
				|  |  | +        self.layout.addWidget(setupCollapsibleButton)
 | 
	
		
			
				|  |  | +        #Form layout (maybe one can think of more intuitive layouts)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        setupFormLayout = qt.QFormLayout(setupCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.serverList=qt.QComboBox()
 | 
	
		
			
				|  |  | +        self.serverList.addItem('<Select>')
 | 
	
		
			
				|  |  | +        self.serverList.addItem("astuden")
 | 
	
		
			
				|  |  | +        self.serverList.addItem("llezaic")
 | 
	
		
			
				|  |  | +        self.serverList.currentIndexChanged.connect(self.onServerListChanged)
 | 
	
		
			
				|  |  | +        setupFormLayout.addRow("Select user:",self.serverList)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.setupList=qt.QComboBox()
 | 
	
		
			
				|  |  | +        self.setupList.addItem('<Select>')
 | 
	
		
			
				|  |  | +        #self.setupList.addItem("limfomiPET_iBrowser.json")
 | 
	
		
			
				|  |  | +        #self.setupList.addItem("limfomiPET_iBrowser_selected.json")
 | 
	
		
			
				|  |  | +        #self.setupList.addItem("iraemm_iBrowserProspective.json")
 | 
	
		
			
				|  |  | +        #self.setupList.addItem("iraemm_iBrowserRetrospective.json")
 | 
	
		
			
				|  |  | +        self.setupList.addItem("cardiacSpect_iBrowser.json")
 | 
	
		
			
				|  |  | +        self.setupList.currentIndexChanged.connect(self.onSetupListChanged)
 | 
	
		
			
				|  |  | +        setupFormLayout.addRow("Setup:",self.setupList)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def addSegmentEditor(self):
 | 
	
		
			
				|  |  | +        editorCollapsibleButton = ctk.ctkCollapsibleButton()
 | 
	
		
			
				|  |  | +        editorCollapsibleButton.text = "Segment Editor"
 | 
	
		
			
				|  |  | +        self.layout.addWidget(editorCollapsibleButton)
 | 
	
		
			
				|  |  | +        hLayout=qt.QVBoxLayout(editorCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.segmentEditorWidget=slicer.qMRMLSegmentEditorWidget()
 | 
	
		
			
				|  |  | +        hLayout.addWidget(self.segmentEditorWidget)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
 | 
	
		
			
				|  |  | +        segEditorNode=slicer.vtkMRMLSegmentEditorNode()
 | 
	
		
			
				|  |  | +        slicer.mrmlScene.AddNode(segEditorNode)
 | 
	
		
			
				|  |  | +        self.segmentEditorWidget.setMRMLSegmentEditorNode(segEditorNode)
 | 
	
		
			
				|  |  | +       
 | 
	
		
			
				|  |  | +    def addWindowManipulator(self):
 | 
	
		
			
				|  |  | +        windowManipulatorCollapsibleButton=ctk.ctkCollapsibleButton()
 | 
	
		
			
				|  |  | +        windowManipulatorCollapsibleButton.text="CT Window Manipulator"
 | 
	
		
			
				|  |  | +        self.layout.addWidget(windowManipulatorCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        hLayout=qt.QHBoxLayout(windowManipulatorCollapsibleButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ctWins={'CT:bone':self.onCtBoneButtonClicked,
 | 
	
		
			
				|  |  | +            'CT:air':self.onCtAirButtonClicked,
 | 
	
		
			
				|  |  | +            'CT:abdomen':self.onCtAbdomenButtonClicked,
 | 
	
		
			
				|  |  | +            'CT:brain':self.onCtBrainButtonClicked,
 | 
	
		
			
				|  |  | +            'CT:lung':self.onCtLungButtonClicked}
 | 
	
		
			
				|  |  | +        for ctWin in ctWins:
 | 
	
		
			
				|  |  | +            ctButton=qt.QPushButton(ctWin)
 | 
	
		
			
				|  |  | +            ctButton.clicked.connect(ctWins[ctWin])
 | 
	
		
			
				|  |  | +            hLayout.addWidget(ctButton)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onSetupListChanged(self,i):
 | 
	
		
			
				|  |  | +        status=self.logic.setConfig(self.setupList.currentText)
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            if status['error']=='FILE NOT FOUND':
 | 
	
		
			
				|  |  | +                print('File {} not found.'.format(self.setupList.currentText))
 | 
	
		
			
				|  |  | +                return
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #sort ids
 | 
	
		
			
				|  |  | +        ids=status['ids']
 | 
	
		
			
				|  |  | +        ids.sort()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.updatePatientList(ids)
 | 
	
		
			
				|  |  | +        self.onPatientListChanged(0)
 | 
	
		
			
				|  |  | + 
 | 
	
		
			
				|  |  | +    def onServerListChanged(self,i):
 | 
	
		
			
				|  |  | +        status=self.logic.setServer(self.serverList.currentText)
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            if status['error']=='KEY ERROR':
 | 
	
		
			
				|  |  | +                self.serverList.setStyleSheet('background-color: violet')
 | 
	
		
			
				|  |  | +            if status['error']=='ID ERROR':
 | 
	
		
			
				|  |  | +                self.serverList.setStyleSheet('background-color: red')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            pass 
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.idField.setText(status['id'])
 | 
	
		
			
				|  |  | +        self.userField.setText(status['displayName'])
 | 
	
		
			
				|  |  | +        self.serverList.setStyleSheet('background-color: green')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onPatientListChanged(self,i):
 | 
	
		
			
				|  |  | +        self.visitList.clear()   
 | 
	
		
			
				|  |  | +        self.petCode.setText("")
 | 
	
		
			
				|  |  | +        self.ctCode.setText("")
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        #add potential filters from setup to dbFilter
 | 
	
		
			
				|  |  | +        ds=self.logic.getDataset(dbFilter={'participant':self.patientList.currentText})
 | 
	
		
			
				|  |  | +       
 | 
	
		
			
				|  |  | +        visitVar=self.logic.getVarName(var='visitField')
 | 
	
		
			
				|  |  | +        dt=datetime.datetime
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        #label is a combination of sequence number and date of imaging
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            seq={row['SequenceNum']:
 | 
	
		
			
				|  |  | +                {'label':row[visitVar],
 | 
	
		
			
				|  |  | +                'date': dt.strptime(row['studyDate'],'%Y/%m/%d %H:%M:%S')}
 | 
	
		
			
				|  |  | +                for row in ds['rows']}
 | 
	
		
			
				|  |  | +        except TypeError:
 | 
	
		
			
				|  |  | +            #if studyDate is empty, this will return no possible visits
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #apply lookup to visitVar if available
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            seq={x:
 | 
	
		
			
				|  |  | +                    {'label':ds['lookups'][visitVar][seq[x]['label']],
 | 
	
		
			
				|  |  | +                    'date':seq[x]['date']}
 | 
	
		
			
				|  |  | +                for x in seq}
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #format label
 | 
	
		
			
				|  |  | +        seq={x:'{} ({})'.format(seq[x]['label'],dt.strftime(seq[x]['date'],'%d-%b-%Y')) 
 | 
	
		
			
				|  |  | +            for x in seq}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        for s in seq:
 | 
	
		
			
				|  |  | +            #onVisitListChanged is called for every addItem
 | 
	
		
			
				|  |  | +            self.visitList.addItem(seq[s],s)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #self.onVisitListChanged(0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onVisitListChanged(self,i):
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        #ignore calls on empty list
 | 
	
		
			
				|  |  | +        if self.visitList.count==0:
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #get sequence num
 | 
	
		
			
				|  |  | +        s=self.visitList.itemData(i)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        print("Visit: SequenceNum:{}, label{}".format(s,self.visitList.currentText))
 | 
	
		
			
				|  |  | +        dbFilter={'participant':self.patientList.currentText,
 | 
	
		
			
				|  |  | +            'seqNum':s}
 | 
	
		
			
				|  |  | +        ds=self.logic.getDataset(dbFilter=dbFilter)
 | 
	
		
			
				|  |  | +        if not len(ds['rows'])==1:
 | 
	
		
			
				|  |  | +            print("Found incorrect number {} of matches for [{}]/[{}]".\
 | 
	
		
			
				|  |  | +                  format(len(ds['rows']),\
 | 
	
		
			
				|  |  | +                  self.patientList.currentText,s))
 | 
	
		
			
				|  |  | +        row=ds['rows'][0]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #copy row properties for data access
 | 
	
		
			
				|  |  | +        self.currentRow=row
 | 
	
		
			
				|  |  | +        self.spectCode.setText(row[self.spectField.text])
 | 
	
		
			
				|  |  | +        self.ctCode.setText(row[self.ctField.text])
 | 
	
		
			
				|  |  | +        #self.segmentationCode.setText(row[self.segmentationField.text])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def updatePatientList(self,ids):
 | 
	
		
			
				|  |  | +        self.patientList.clear()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for id in ids:
 | 
	
		
			
				|  |  | +            self.patientList.addItem(id)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onPatientLoadButtonClicked(self):
 | 
	
		
			
				|  |  | +        print("Load")
 | 
	
		
			
				|  |  | +        #delegate loading to logic
 | 
	
		
			
				|  |  | +        self.logic.loadImages(self.currentRow,self.keepCached.isChecked(),
 | 
	
		
			
				|  |  | +                              self.forceReload.isChecked())
 | 
	
		
			
				|  |  | +        self.logic.loadSegmentation(self.currentRow)
 | 
	
		
			
				|  |  | +        self.setSegmentEditor()
 | 
	
		
			
				|  |  | +        #self.logic.loadReview(self.currentRow)
 | 
	
		
			
				|  |  | +        #self.logic.loadAE(self.currentRow)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #self.onReviewSegmentChanged()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setSegmentEditor(self):
 | 
	
		
			
				|  |  | +        #use current row to set segment in segment editor
 | 
	
		
			
				|  |  | +        self.segmentEditorWidget.setSegmentationNode(
 | 
	
		
			
				|  |  | +            self.logic.volumeNode['Segmentation'])
 | 
	
		
			
				|  |  | +        self.segmentEditorWidget.setMasterVolumeNode(
 | 
	
		
			
				|  |  | +            self.logic.volumeNode['PET'])
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +    def onReviewSegmentChanged(self):
 | 
	
		
			
				|  |  | +        pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onPatientClearButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.clearVolumesAndSegmentations()
 | 
	
		
			
				|  |  | +        self.patientSave.setStyleSheet('background-color:gray')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onPatientSaveButtonClicked(self):
 | 
	
		
			
				|  |  | +        status=self.logic.saveSegmentation()
 | 
	
		
			
				|  |  | +        if status:
 | 
	
		
			
				|  |  | +            self.patientSave.setStyleSheet('background-color:green')
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            self.patientSave.setStyleSheet('background-color:red')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onCtBoneButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.setWindow('CT:bone')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onCtAirButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.setWindow('CT:air')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onCtAbdomenButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.setWindow('CT:abdomen')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onCtBrainButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.setWindow('CT:brain')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def onCtLungButtonClicked(self):
 | 
	
		
			
				|  |  | +        self.logic.setWindow('CT:lung')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def cleanup(self):
 | 
	
		
			
				|  |  | +        pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def loadLibrary(name):
 | 
	
		
			
				|  |  | +    #utility function to load nix library from git
 | 
	
		
			
				|  |  | +    fwrapper=nixModule.getWrapper('nixWrapper.py')
 | 
	
		
			
				|  |  | +    p=pathlib.Path(fwrapper)
 | 
	
		
			
				|  |  | +    sys.path.append(str(p.parent))
 | 
	
		
			
				|  |  | +    import nixWrapper
 | 
	
		
			
				|  |  | +    return nixWrapper.loadLibrary(name)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +# imageBrowserLogic
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class imageBrowserLogic(ScriptedLoadableModuleLogic):
 | 
	
		
			
				|  |  | +    """This class should implement all the actual
 | 
	
		
			
				|  |  | +  computation done by your module.  The interface
 | 
	
		
			
				|  |  | +  should be such that other python code can import
 | 
	
		
			
				|  |  | +  this class and make use of the functionality without
 | 
	
		
			
				|  |  | +  requiring an instance of the Widget.
 | 
	
		
			
				|  |  | +  Uses ScriptedLoadableModuleLogic base class, available at:
 | 
	
		
			
				|  |  | +  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    def __init__(self,parent=None):
 | 
	
		
			
				|  |  | +        ScriptedLoadableModuleLogic.__init__(self, parent)
 | 
	
		
			
				|  |  | +        print('imageBrowserLogic loading')
 | 
	
		
			
				|  |  | +        if not parent==None:
 | 
	
		
			
				|  |  | +            #use layout and data from parent widget
 | 
	
		
			
				|  |  | +            self.parent=parent
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        fhome=os.path.expanduser('~')
 | 
	
		
			
				|  |  | +        fsetup=os.path.join(fhome,'.labkey','setup.json')
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +             with open(fsetup) as f:
 | 
	
		
			
				|  |  | +                self.setup=json.load(f)
 | 
	
		
			
				|  |  | +        except FileNotFoundError:
 | 
	
		
			
				|  |  | +            self.setup={}
 | 
	
		
			
				|  |  | +          
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            pt=self.setup['paths']
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            self.setup['paths']={}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        lName='labkeyInterface'
 | 
	
		
			
				|  |  | +        loadLibrary(lName)
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +        import labkeyInterface
 | 
	
		
			
				|  |  | +        import labkeyDatabaseBrowser
 | 
	
		
			
				|  |  | +        import labkeyFileBrowser
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.network=labkeyInterface.labkeyInterface() 
 | 
	
		
			
				|  |  | +        self.dbBrowser=labkeyDatabaseBrowser
 | 
	
		
			
				|  |  | +        self.fBrowser=labkeyFileBrowser
 | 
	
		
			
				|  |  | +        self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
 | 
	
		
			
				|  |  | +        if not os.path.isdir(self.tempDir):
 | 
	
		
			
				|  |  | +            os.mkdir(self.tempDir)
 | 
	
		
			
				|  |  | +        self.lookups={}
 | 
	
		
			
				|  |  | +        #self.segmentList=["Liver","Blood","Marrow","Disease","Deauville"]
 | 
	
		
			
				|  |  | +        self.segmentList=["One","Two","Three","Four","Five","Six","Seven","Eight"]
 | 
	
		
			
				|  |  | +        print('imageBrowserLogic setup complete')
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setServer(self,serverName):
 | 
	
		
			
				|  |  | +        #additional way of setting the labkey network interface 
 | 
	
		
			
				|  |  | +        #if no parent was provided in logic initialization (stand-alone mode)
 | 
	
		
			
				|  |  | +        status={}
 | 
	
		
			
				|  |  | +        fileName="NONE"
 | 
	
		
			
				|  |  | +        if serverName=="astuden":
 | 
	
		
			
				|  |  | +            fileName="astuden.json"
 | 
	
		
			
				|  |  | +        if serverName=="llezaic":
 | 
	
		
			
				|  |  | +            fileName="llezaic.json"
 | 
	
		
			
				|  |  | +        if fileName=="NONE":
 | 
	
		
			
				|  |  | +            print("No path was associated with server {}".format(serverName))
 | 
	
		
			
				|  |  | +            status['error']='KEY ERROR'
 | 
	
		
			
				|  |  | +            return status
 | 
	
		
			
				|  |  | +        fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
 | 
	
		
			
				|  |  | +        self.network.init(fconfig)
 | 
	
		
			
				|  |  | +        self.remoteId=self.network.getUserId()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if self.remoteId==None:
 | 
	
		
			
				|  |  | +            status['error']='ID ERROR'
 | 
	
		
			
				|  |  | +            return status
 | 
	
		
			
				|  |  | +     
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        status['displayName']=self.remoteId['displayName']
 | 
	
		
			
				|  |  | +        status['id']=self.remoteId['id']
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #reset db and fb (they are thin classes anyhow)
 | 
	
		
			
				|  |  | +        self.db=self.dbBrowser.labkeyDB(self.network)
 | 
	
		
			
				|  |  | +        self.fb=self.fBrowser.labkeyFileBrowser(self.network)
 | 
	
		
			
				|  |  | +        return status
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setConfig(self,configName):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        status={}
 | 
	
		
			
				|  |  | +        fileName=os.path.join(os.path.expanduser('~'),'.labkey',configName)
 | 
	
		
			
				|  |  | +        if not os.path.isfile(fileName):
 | 
	
		
			
				|  |  | +            status['error']='FILE NOT FOUND'
 | 
	
		
			
				|  |  | +            return status
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        with open(fileName,'r') as f:
 | 
	
		
			
				|  |  | +            self.isetup=json.load(f)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #self.project=self.isetup['project']
 | 
	
		
			
				|  |  | +        #"iPNUMMretro/Study"
 | 
	
		
			
				|  |  | +        #self.schema='study'
 | 
	
		
			
				|  |  | +        #self.dataset=self.isetup['query']
 | 
	
		
			
				|  |  | +   
 | 
	
		
			
				|  |  | +        #include filters...
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ds=self.getDataset()
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            filterValue=self.isetup['filterEntries']
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            #this is default
 | 
	
		
			
				|  |  | +            ids=[row[self.isetup['participantField']] for row in ds['rows']]
 | 
	
		
			
				|  |  | +            status['ids']=list(set(ids))
 | 
	
		
			
				|  |  | +            return status
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        #look for entries where segmentation was already done
 | 
	
		
			
				|  |  | +        dsSet=self.getDataset('SegmentationsMaster')
 | 
	
		
			
				|  |  | +        segMap={'{}:{}'.format(r['ParticipantId'],r['visitCode']):r['comments'] 
 | 
	
		
			
				|  |  | +                for r in dsSet['rows']}
 | 
	
		
			
				|  |  | +        ids=[]
 | 
	
		
			
				|  |  | +        for r in ds['rows']:
 | 
	
		
			
				|  |  | +            code='{}:{}'.format(r['ParticipantId'],r['visitCode'])
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                comment=segMap[code]
 | 
	
		
			
				|  |  | +            except KeyError:
 | 
	
		
			
				|  |  | +                ids.append(r['ParticipantId'])
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            if comment==filterValue:
 | 
	
		
			
				|  |  | +                ids.append(r['ParticipantId'])
 | 
	
		
			
				|  |  | +        status['ids']=list(set(ids))
 | 
	
		
			
				|  |  | +        return status
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def getVarName(self,name="Imaging",var="visitField"):
 | 
	
		
			
				|  |  | +        dset=self.isetup['datasets'][name]
 | 
	
		
			
				|  |  | +        defaults={"visitField":"imagingVisitId"}
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            return dset[var]
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            return defaults[var]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +    def getDataset(self,name="Imaging",dbFilter={}):
 | 
	
		
			
				|  |  | +        dset=self.isetup['datasets'][name]
 | 
	
		
			
				|  |  | +        project=dset['project']
 | 
	
		
			
				|  |  | +        schema=dset['schema']
 | 
	
		
			
				|  |  | +        query=dset['query']
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #add default filters
 | 
	
		
			
				|  |  | +        qFilter=[]
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            for qf in dset['filter']:
 | 
	
		
			
				|  |  | +                v=dset['filter'][qf]
 | 
	
		
			
				|  |  | +                qFilter.append({'variable':qf,'value':v,'oper':'eq'})
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +        for f in dbFilter:
 | 
	
		
			
				|  |  | +            if f=='participant':
 | 
	
		
			
				|  |  | +                qFilter.append({'variable':self.isetup['participantField'],
 | 
	
		
			
				|  |  | +                    'value':dbFilter[f],'oper':'eq'})
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            if f=='seqNum':
 | 
	
		
			
				|  |  | +                qFilter.append({'variable':'SequenceNum',
 | 
	
		
			
				|  |  | +                    'value':'{}'.format(dbFilter[f]),
 | 
	
		
			
				|  |  | +                    'oper':'eq'})
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            qFilter.append({'variable':f,'value':dbFilter[f],'oper':'eq'})
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            ds=self.db.selectRows(project,schema,query, \
 | 
	
		
			
				|  |  | +                qFilter,dset['view'])
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            ds=self.db.selectRows(project,schema,query,qFilter)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #get lookups as well 
 | 
	
		
			
				|  |  | +        lookups={}
 | 
	
		
			
				|  |  | +        for f in ds['metaData']['fields']:
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                lookup=f['lookup']
 | 
	
		
			
				|  |  | +            except KeyError:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            var=f['name']
 | 
	
		
			
				|  |  | +            lookupCode='{}:{}'.format(lookup['schema'],lookup['queryName'])
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                lookups[var]=self.lookups[lookupCode]
 | 
	
		
			
				|  |  | +            except KeyError:
 | 
	
		
			
				|  |  | +                self.lookups[lookupCode]=self.loadLookup(project,lookup)
 | 
	
		
			
				|  |  | +                lookups[var]=self.lookups[lookupCode]
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        return {'rows':ds['rows'],'lookups':lookups}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def loadLookup(self,project,lookup):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        qData={}
 | 
	
		
			
				|  |  | +        key=lookup['keyColumn']
 | 
	
		
			
				|  |  | +        value=lookup['displayColumn']
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        fSet=self.db.selectRows(project,lookup['schema'],lookup['queryName'],[])
 | 
	
		
			
				|  |  | +        for q in fSet['rows']:
 | 
	
		
			
				|  |  | +            qData[q[key]]=q[value]
 | 
	
		
			
				|  |  | +        return qData
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def loadImage(self,iData):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #unpack iData
 | 
	
		
			
				|  |  | +        idx=iData['idx']
 | 
	
		
			
				|  |  | +        path=iData['path']
 | 
	
		
			
				|  |  | +        keepCached=iData['keepCached']
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            forceReload=iData['forceReload']
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            forceReload=False
 | 
	
		
			
				|  |  | +        dset=self.isetup['datasets'][iData['dataset']]
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        localPath=os.path.join(self.tempDir,path[-1])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if not os.path.isfile(localPath) or forceReload:
 | 
	
		
			
				|  |  | +            #download from server
 | 
	
		
			
				|  |  | +            remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
 | 
	
		
			
				|  |  | +            if not self.fb.entryExists(remotePath):
 | 
	
		
			
				|  |  | +                print("Failed to get {}".format(remotePath))
 | 
	
		
			
				|  |  | +                return
 | 
	
		
			
				|  |  | +            #overwrites existing file from remote
 | 
	
		
			
				|  |  | +            self.fb.readFileToFile(remotePath,localPath) 
 | 
	
		
			
				|  |  | +          
 | 
	
		
			
				|  |  | +        properties={}
 | 
	
		
			
				|  |  | +        filetype='VolumeFile'
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #make sure segmentation gets loaded as a labelmap
 | 
	
		
			
				|  |  | +        if idx=="Segmentation":
 | 
	
		
			
				|  |  | +            filetype='SegmentationFile'
 | 
	
		
			
				|  |  | +            #properties["labelmap"]=1
 | 
	
		
			
				|  |  | +              
 | 
	
		
			
				|  |  | +          
 | 
	
		
			
				|  |  | +        self.volumeNode[idx]=slicer.util.loadNodeFromFile(localPath,
 | 
	
		
			
				|  |  | +                        filetype=filetype,properties=properties)
 | 
	
		
			
				|  |  | +          
 | 
	
		
			
				|  |  | +        if not keepCached:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +            #os.remove(localPath)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def loadImages(self,row,keepCached, forceReload=False):
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  | +        #fields={'ctResampled':True,'petResampled':False}
 | 
	
		
			
				|  |  | +        fields={"CT":self.parent.ctField.text,\
 | 
	
		
			
				|  |  | +              "SPECT":self.parent.spectField.text}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
 | 
	
		
			
				|  |  | +        relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.volumeNode={}
 | 
	
		
			
				|  |  | +        for f in relativePaths:
 | 
	
		
			
				|  |  | +            iData={'idx':f,'path':relativePaths[f],
 | 
	
		
			
				|  |  | +                    'keepCached':keepCached,'dataset':'Imaging',
 | 
	
		
			
				|  |  | +                    'forceReload':forceReload}
 | 
	
		
			
				|  |  | +            self.loadImage(iData)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #mimic abdominalCT standardized window setting
 | 
	
		
			
				|  |  | +        self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
 | 
	
		
			
				|  |  | +              SetAutoWindowLevel(False)
 | 
	
		
			
				|  |  | +        self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
 | 
	
		
			
				|  |  | +              SetWindowLevel(1400, -500)
 | 
	
		
			
				|  |  | +        #set colormap for PET to PET-Heat (this is a verbatim setting from
 | 
	
		
			
				|  |  | +        #the Volumes->Display->Lookup Table colormap identifier)
 | 
	
		
			
				|  |  | +        self.volumeNode['SPECT'].GetScalarVolumeDisplayNode().\
 | 
	
		
			
				|  |  | +            SetAndObserveColorNodeID(\
 | 
	
		
			
				|  |  | +            slicer.util.getNode('Inferno').GetID())
 | 
	
		
			
				|  |  | +        slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
 | 
	
		
			
				|  |  | +            foreground=self.volumeNode['SPECT'],foregroundOpacity=0.5,fit=True)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def loadSegmentation(self,row, loadFile=1):
 | 
	
		
			
				|  |  | +        dbFilter={'User':'{}'.format(self.remoteId['id']),
 | 
	
		
			
				|  |  | +            'participant':row[self.isetup['participantField']],
 | 
	
		
			
				|  |  | +            'visitCode':row['visitCode']}
 | 
	
		
			
				|  |  | +        ds=self.getDataset(name='SegmentationsMaster',
 | 
	
		
			
				|  |  | +                dbFilter=dbFilter)
 | 
	
		
			
				|  |  | +        if len(ds['rows'])>1:
 | 
	
		
			
				|  |  | +            print('Multiple segmentations found!')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        if len(ds['rows'])==1:
 | 
	
		
			
				|  |  | +            #update self.segmentationEntry
 | 
	
		
			
				|  |  | +            self.segmentationEntry=ds['rows'][0]
 | 
	
		
			
				|  |  | +            self.segmentationEntry['origin']='database'
 | 
	
		
			
				|  |  | +            if loadFile:
 | 
	
		
			
				|  |  | +                self.loadSegmentationFromEntry()
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        #create new segmentation 
 | 
	
		
			
				|  |  | +        self.createSegmentation(row)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def getSegmentationPath(self):
 | 
	
		
			
				|  |  | +        path=[self.isetup['imageDir'],
 | 
	
		
			
				|  |  | +                self.segmentationEntry['patientCode'],
 | 
	
		
			
				|  |  | +                self.segmentationEntry['visitCode']]
 | 
	
		
			
				|  |  | +        path.append('Segmentations')
 | 
	
		
			
				|  |  | +        return path
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def loadSegmentationFromEntry(self):
 | 
	
		
			
				|  |  | +        #compile path
 | 
	
		
			
				|  |  | +        entry=self.segmentationEntry
 | 
	
		
			
				|  |  | +        path=self.getSegmentationPath()
 | 
	
		
			
				|  |  | +        path.append(entry['latestFile'])
 | 
	
		
			
				|  |  | +        iData={'idx':'Segmentation','path':path,
 | 
	
		
			
				|  |  | +                    'keepCached':1,'dataset':'SegmentationsMaster'}
 | 
	
		
			
				|  |  | +        self.loadImage(iData)
 | 
	
		
			
				|  |  | +        #look for missing segments
 | 
	
		
			
				|  |  | +        segNode=self.volumeNode['Segmentation']
 | 
	
		
			
				|  |  | +        seg=segNode.GetSegmentation()
 | 
	
		
			
				|  |  | +        segNames=[seg.GetNthSegmentID(i) for i in range(seg.GetNumberOfSegments())]
 | 
	
		
			
				|  |  | +        print('Segments')
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            segmentList=self.isetup['segmentList']
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            segmentList=self.segmentList
 | 
	
		
			
				|  |  | +        for s in segmentList:
 | 
	
		
			
				|  |  | +            if s not in segNames:
 | 
	
		
			
				|  |  | +                seg.AddEmptySegment(s,s)
 | 
	
		
			
				|  |  | +                print(s)
 | 
	
		
			
				|  |  | +        print('Done')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def saveSegmentation(self):
 | 
	
		
			
				|  |  | +        #get the latest key by adding an entry to SegmentationList
 | 
	
		
			
				|  |  | +        copyFields=[self.isetup['participantField'],'patientCode','visitCode','User']
 | 
	
		
			
				|  |  | +        outRow={x:self.segmentationEntry[x] for x in copyFields}
 | 
	
		
			
				|  |  | +        sList=self.isetup['datasets']['SegmentationsList']
 | 
	
		
			
				|  |  | +        resp=self.db.modifyRows('insert',sList['project'],
 | 
	
		
			
				|  |  | +            sList['schema'],sList['query'],[outRow])
 | 
	
		
			
				|  |  | +        encoding=chardet.detect(resp)['encoding']
 | 
	
		
			
				|  |  | +        respJSON=json.loads(resp.decode(encoding))
 | 
	
		
			
				|  |  | +        outRow=respJSON['rows'][0]
 | 
	
		
			
				|  |  | +        #print(outRow)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #construct file name with known key
 | 
	
		
			
				|  |  | +        uName=re.sub(' ','_',self.remoteId['displayName'])
 | 
	
		
			
				|  |  | +        fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
 | 
	
		
			
				|  |  | +                self.segmentationEntry['patientCode'],
 | 
	
		
			
				|  |  | +                self.segmentationEntry['visitCode'],
 | 
	
		
			
				|  |  | +                uName,outRow['Key'])
 | 
	
		
			
				|  |  | +        path=self.getSegmentationPath()
 | 
	
		
			
				|  |  | +        path.append(fName)
 | 
	
		
			
				|  |  | +        status=self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        #update SegmentationList with know file name
 | 
	
		
			
				|  |  | +        outRow['Segmentation']=fName
 | 
	
		
			
				|  |  | +        self.db.modifyRows('update',sList['project'],
 | 
	
		
			
				|  |  | +            sList['schema'],sList['query'],[outRow])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #update SegmentationsMaster
 | 
	
		
			
				|  |  | +        self.segmentationEntry['latestFile']=fName
 | 
	
		
			
				|  |  | +        self.segmentationEntry['version']=outRow['Key']
 | 
	
		
			
				|  |  | +        des=self.isetup['datasets']['SegmentationsMaster']
 | 
	
		
			
				|  |  | +        op='insert'
 | 
	
		
			
				|  |  | +        if self.segmentationEntry['origin']=='database':
 | 
	
		
			
				|  |  | +            op='update'
 | 
	
		
			
				|  |  | +        print('Saving: mode={}'.format(op))
 | 
	
		
			
				|  |  | +        resp=self.db.modifyRows(op,des['project'],
 | 
	
		
			
				|  |  | +                des['schema'],des['query'],[self.segmentationEntry])
 | 
	
		
			
				|  |  | +        print(resp)
 | 
	
		
			
				|  |  | +        #since we loaded a version, origin should be set to database
 | 
	
		
			
				|  |  | +        self.loadSegmentation(self.segmentationEntry,0)
 | 
	
		
			
				|  |  | +        return status
 | 
	
		
			
				|  |  | +        #self.segmentationEntry['origin']='database'
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def saveNode(self,node,dataset,path):
 | 
	
		
			
				|  |  | +        fName=path[-1]
 | 
	
		
			
				|  |  | +        localPath=os.path.join(self.tempDir,fName)
 | 
	
		
			
				|  |  | +        slicer.util.saveNode(node,localPath)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        dset=self.isetup['datasets'][dataset]
 | 
	
		
			
				|  |  | +        #exclude file name when building directory structure
 | 
	
		
			
				|  |  | +        remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
 | 
	
		
			
				|  |  | +        remotePath+='/'+fName
 | 
	
		
			
				|  |  | +        self.fb.writeFileToFile(localPath,remotePath)
 | 
	
		
			
				|  |  | +        return self.fb.entryExists(remotePath)
 | 
	
		
			
				|  |  | +        #add entry to segmentation list
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def createSegmentation(self,entry):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #create segmentation entry for database update
 | 
	
		
			
				|  |  | +        #note that origin is not set to database
 | 
	
		
			
				|  |  | +        copyFields=[self.isetup['participantField'],'patientCode','visitCode','SequenceNum']
 | 
	
		
			
				|  |  | +        #copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
 | 
	
		
			
				|  |  | +        self.segmentationEntry={x:entry[x] for x in copyFields}
 | 
	
		
			
				|  |  | +        self.segmentationEntry['User']=self.remoteId['id']
 | 
	
		
			
				|  |  | +        self.segmentationEntry['origin']='created'
 | 
	
		
			
				|  |  | +        self.segmentationEntry['version']=-1111
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +        #create a segmentation node
 | 
	
		
			
				|  |  | +        uName=re.sub(' ','_',self.remoteId['displayName'])
 | 
	
		
			
				|  |  | +        segNode=slicer.vtkMRMLSegmentationNode()
 | 
	
		
			
				|  |  | +        self.volumeNode['Segmentation']=segNode
 | 
	
		
			
				|  |  | +        segNode.SetName('Segmentation_{}_{}_{}'.
 | 
	
		
			
				|  |  | +                format(entry['patientCode'],entry['visitCode'],uName))
 | 
	
		
			
				|  |  | +        slicer.mrmlScene.AddNode(segNode)
 | 
	
		
			
				|  |  | +        segNode.CreateDefaultDisplayNodes()
 | 
	
		
			
				|  |  | +        segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['PET'])
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            segmentList=self.isetup['segmentList']
 | 
	
		
			
				|  |  | +        except KeyError:
 | 
	
		
			
				|  |  | +            segmentList=self.segmentList
 | 
	
		
			
				|  |  | +        for s in segmentList:
 | 
	
		
			
				|  |  | +            segNode.GetSegmentation().AddEmptySegment(s,s)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def clearVolumesAndSegmentations(self):
 | 
	
		
			
				|  |  | +        nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
 | 
	
		
			
				|  |  | +        nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
 | 
	
		
			
				|  |  | +        res=[slicer.mrmlScene.RemoveNode(f) for f in nodes] 
 | 
	
		
			
				|  |  | +        #self.segmentationNode=None
 | 
	
		
			
				|  |  | +        #self.reviewResult={}
 | 
	
		
			
				|  |  | +        #self.aeList={}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setWindow(self,windowName):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #default
 | 
	
		
			
				|  |  | +        w=1400
 | 
	
		
			
				|  |  | +        l=-500
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if windowName=='CT:bone':
 | 
	
		
			
				|  |  | +            w=1000
 | 
	
		
			
				|  |  | +            l=400
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if windowName=='CT:air':
 | 
	
		
			
				|  |  | +            w=1000
 | 
	
		
			
				|  |  | +            l=-426
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if windowName=='CT:abdomen':
 | 
	
		
			
				|  |  | +            w=350
 | 
	
		
			
				|  |  | +            l=40
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if windowName=='CT:brain':
 | 
	
		
			
				|  |  | +            w=100
 | 
	
		
			
				|  |  | +            l=50
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if windowName=='CT:lung':
 | 
	
		
			
				|  |  | +            w=1400
 | 
	
		
			
				|  |  | +            l=-500
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
 | 
	
		
			
				|  |  | +              SetWindowLevel(w,l)
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        print('setWindow: {} {}/{}'.format(windowName,w,l))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class imageBrowserTest(ScriptedLoadableModuleTest):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +  This is the test case for your scripted module.
 | 
	
		
			
				|  |  | +  Uses ScriptedLoadableModuleTest base class, available at:
 | 
	
		
			
				|  |  | +  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def setup(self):
 | 
	
		
			
				|  |  | +        """ Do whatever is needed to reset the state - typically a scene clear will be enough.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        slicer.mrmlScene.Clear(0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def runTest(self):
 | 
	
		
			
				|  |  | +        """Run as few or as many tests as needed here.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        self.setUp()
 | 
	
		
			
				|  |  | +        self.test_irAEMMBrowser()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def test_irAEMMBrowser(self):
 | 
	
		
			
				|  |  | +        """ Ideally you should have several levels of tests.  At the lowest level
 | 
	
		
			
				|  |  | +    tests sould exercise the functionality of the logic with different inputs
 | 
	
		
			
				|  |  | +    (both valid and invalid).  At higher levels your tests should emulate the
 | 
	
		
			
				|  |  | +    way the user would interact with your code and confirm that it still works
 | 
	
		
			
				|  |  | +    the way you intended.
 | 
	
		
			
				|  |  | +    One of the most important features of the tests is that it should alert other
 | 
	
		
			
				|  |  | +    developers when their changes will have an impact on the behavior of your
 | 
	
		
			
				|  |  | +    module.  For example, if a developer removes a feature that you depend on,
 | 
	
		
			
				|  |  | +    your test should break so they know that the feature is needed.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.delayDisplay("Starting the test")
 | 
	
		
			
				|  |  | +        #
 | 
	
		
			
				|  |  | +        # first, get some data
 | 
	
		
			
				|  |  | +        #
 | 
	
		
			
				|  |  | +
 |