| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911 | import osimport unittestfrom __main__ import vtk, qt, ctk, slicerfrom slicer.ScriptedLoadableModule import *import jsonimport datetimeimport sysimport nixModuleimport pathlibimport chardetimport 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.petField=qt.QLabel("petResampled")        infoLayout.addRow("Data field (PET):",self.petField)        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("adoma")        self.serverList.addItem("kzevnik")        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("ORLPET_iBrowser.json")        self.setupList.currentIndexChanged.connect(self.onSetupListChanged)        setupFormLayout.addRow("Setup:",self.setupList)    def addReviewSection(self):        #        # Review Area        #        reviewCollapsibleButton = ctk.ctkCollapsibleButton()        reviewCollapsibleButton.text = "Review"        self.layout.addWidget(reviewCollapsibleButton)            self.reviewBoxLayout = qt.QVBoxLayout(reviewCollapsibleButton)        self.reviewFormLayout = qt.QFormLayout()        self.reviewSegment=qt.QComboBox()        self.reviewSegment.currentIndexChanged.connect(\                self.onReviewSegmentChanged)        self.reviewFormLayout.addRow("Selected region:",self.reviewSegment)               self.reviewResult=qt.QComboBox()        sLabel="What do you think about the segmentation:"        self.reviewFormLayout.addRow(sLabel,self.reviewResult)        reviewOptions=['Select','Excellent','Minor deficiencies',\            'Major deficiencies','Unusable']        for opt in reviewOptions:            self.reviewResult.addItem(opt)            self.aeResult=qt.QComboBox()        aeLabel="Is organ suffering from adverse effect?"        self.reviewFormLayout.addRow(aeLabel,self.aeResult)        aeOptions=['Select','Yes','No']        for opt in aeOptions:            self.aeResult.addItem(opt)        #self.aeResult.setCurrentIndex(0)        self.updateReview=qt.QPushButton("Save")        saLabel="Save segmentation and AE decision for current segment"        self.reviewFormLayout.addRow(saLabel,self.updateReview)        self.updateReview.clicked.connect(self.onUpdateReviewButtonClicked)        self.reviewBoxLayout.addLayout(self.reviewFormLayout)        submitFrame=qt.QGroupBox("Submit data")            self.submitFormLayout=qt.QFormLayout()        self.reviewComment=qt.QTextEdit("this is a test")        self.submitFormLayout.addRow("Comments (optional)",self.reviewComment)            self.submitReviewButton=qt.QPushButton("Submit")        self.submitFormLayout.addRow("Submit to database",\            self.submitReviewButton)        self.submitReviewButton.clicked.connect(\                self.onSubmitReviewButtonClicked)            submitFrame.setLayout(self.submitFormLayout)        submitFrame.setFlat(1)        #submitFrame.setFrameShape(qt.QFrame.StyledPanel)        #submitFrame.setFrameShadow(qt.QFrame.Sunken)        submitFrame.setStyleSheet("background-color:rgba(220,215,180,45)")        self.reviewBoxLayout.addWidget(submitFrame)    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.petCode.setText(row[self.petField.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):        passdef 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","Metastases"]        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=="adoma":            fileName="adoma.json"        if serverName=="kzevnik":            fileName="kzevnik.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):      #read the config file and adjust the browser window         self.parent.ctField.setText(self.isetup.get('ctField','ctResampled'))        self.parent.petField.setText(self.isetup.get('petField','petResampled'))        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)        #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,\              "PET":self.parent.petField.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['PET'].GetScalarVolumeDisplayNode().\            SetAndObserveColorNodeID(\            slicer.util.getNode('Inferno').GetID())        slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\            foreground=self.volumeNode['PET'],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']        respJSON=self.db.modifyRows('insert',sList['project'],            sList['schema'],sList['query'],[outRow])        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        #
 |