def getMostRecentApprovedTransform(self): seriesTypeManager = SeriesTypeManager() results = sorted(self.registrationResults.values(), key=lambda s: s.seriesNumber) for result in reversed(results): if result.approved and not seriesTypeManager.isCoverProstate(result.name): return result.getTransform(result.registrationType) return None
def __init__(self): StepBasedSession.__init__(self) self.registrationLogic = SliceTrackerRegistrationLogic() self.seriesTypeManager = SeriesTypeManager() self.seriesTypeManager.addEventObserver(self.seriesTypeManager.SeriesTypeManuallyAssignedEvent, lambda caller, event: self.invokeEvent(self.SeriesTypeManuallyAssignedEvent)) self.resetAndInitializeMembers()
def __init__(self): StepBasedSession.__init__(self) self.seriesTypeManager = SeriesTypeManager() self.seriesTypeManager.addEventObserver( self.seriesTypeManager.SeriesTypeManuallyAssignedEvent, lambda caller, event: self.invokeEvent( self.SeriesTypeManuallyAssignedEvent)) self.targetingPlugin = TargetsDefinitionPlugin(self) self.needlePathCaculator = ZFrameGuidanceComputation(self) self.segmentationEditor = slicer.qMRMLSegmentEditorWidget() self.resetAndInitializeMembers() self.resetAndInitializedTargetsAndSegments()
def resetAndInitializeData(self): self.seriesTypeManager = SeriesTypeManager() self.startTimeStamp = self.getTime() self.resumeTimeStamps = [] self.closedLogTimeStamps = [] self.savedNeedleTypeForTargets = dict() self.savedNeedleTypeForTargets.clear() self.completed = False self.segmentModelNode = None self.initialVolume = None self.initialLabel = None self.intraOpTargets = None self.zFrameRegistrationResult = None self.customProgressBar = CustomStatusProgressbar()
def save(self, outputDir): seriesTypeManager = SeriesTypeManager() dictionary = { "name": self.name, "seriesType": seriesTypeManager.getSeriesType(self.name) } savedSuccessfully = [] failedToSave = [] success, name = self.saveNodeData(self.transform, outputDir, FileExtension.H5) dictionary["transform"] = name + FileExtension.H5 self.handleSaveNodeDataReturn(success, name, savedSuccessfully, failedToSave) success, name = self.saveNodeData(self.volume, outputDir, FileExtension.NRRD) dictionary["volume"] = name + FileExtension.NRRD self.handleSaveNodeDataReturn(success, name, savedSuccessfully, failedToSave) return dictionary
def asDict(self): seriesTypeManager = SeriesTypeManager() dictionary = super(RegistrationResult, self).asDict() dictionary.update({ "name": self.name, "series":{ "type": seriesTypeManager.getSeriesType(self.name), "receivedTime": self.receivedTime } }) if self.approved or self.rejected: dictionary["targets"] = self.targets.getAllFileNames() dictionary["transforms"] = self.transforms.getAllFileNames() dictionary["volumes"] = self.volumes.getAllFileNames() dictionary["labels"] = self.labels.getAllFileNames() dictionary["suffix"] = self.suffix if self.startTime and self.endTime: dictionary["registration"] = { "startTime": self.startTime, "endTime": self.endTime } if self.approved: dictionary["status"]["registrationType"] = self.registrationType elif self.skipped: dictionary["volumes"] = { "fixed": self.volumes.getFileName(self.volumes.fixed) } if self.score: dictionary["score"] = self.score if self.approved: dictionary["targets"]["approved"] = { "userModified": self.getApprovedTargetsModifiedStatus(), "fileName": self.targets.getFileNameByAttributeName("approved") } if self.segmentationData: dictionary["segmentation"] = self.segmentationData.toJSON() return dictionary
def resetAndInitializeData(self): self.seriesTypeManager = SeriesTypeManager() self.startTime = self.getTime() self.resumeTimeStamps = [] self.closedLogTimeStamps = [] self.completed = False self.usePreopData = False self.segmentModelNode = None self.inputMarkupNode = None self.initialVolume = None self.initialLabel = None self.initialTargets = None self.initialTargetsPath = None self.zFrameRegistrationResult = None self._savedRegistrationResults = [] self.initializeRegistrationResults() self.customProgressBar = CustomStatusProgressbar() self.alreadyLoadedFileNames = {}
class SessionData(ModuleLogicMixin): NewResultCreatedEvent = vtk.vtkCommand.UserEvent + 901 DEFAULT_JSON_FILE_NAME = "results.json" _completed = False _resumed = False @property def completed(self): return self._completed @completed.setter def completed(self, value): self._completed = value self.completedLogTimeStamp = self.generateLogfileTimeStampDict( ) if self._completed else None @property def resumed(self): return self._resumed @resumed.setter def resumed(self, value): if value and self.completed: raise ValueError("Completed case is not supposed to be resumed.") if value and not self.completed: self.resumeTimeStamps.append(self.getTime()) self._resumed = value @staticmethod def wasSessionCompleted(filename): with open(filename) as data_file: data = json.load(data_file) procedureEvents = data["procedureEvents"] return "caseCompleted" in procedureEvents.keys() def __init__(self): self.resetAndInitializeData() def resetAndInitializeData(self): self.seriesTypeManager = SeriesTypeManager() self.startTimeStamp = self.getTime() self.resumeTimeStamps = [] self.closedLogTimeStamps = [] self.savedNeedleTypeForTargets = dict() self.savedNeedleTypeForTargets.clear() self.completed = False self.segmentModelNode = None self.initialVolume = None self.initialLabel = None self.intraOpTargets = None self.zFrameRegistrationResult = None self.customProgressBar = CustomStatusProgressbar() def createZFrameRegistrationResult(self, series): self.zFrameRegistrationResult = ZFrameRegistrationResult(series) return self.zFrameRegistrationResult def readProcedureEvents(self, procedureEvents): self.startTimeStamp = procedureEvents["caseStarted"] self.completed = "caseCompleted" in procedureEvents.keys() if self.completed: self.completedLogTimeStamp = procedureEvents["caseCompleted"] if "caseClosed" in procedureEvents.keys(): self.closedLogTimeStamps = procedureEvents["caseClosed"] if "caseResumed" in procedureEvents.keys(): self.resumeTimeStamps = procedureEvents["caseResumed"] def load(self, filename): directory = os.path.dirname(filename) self.resetAndInitializeData() self.alreadyLoadedFileNames = {} with open(filename) as data_file: self.customProgressBar.visible = True self.customProgressBar.text = "Reading meta information" logging.debug("reading json file %s" % filename) data = json.load(data_file) self.readProcedureEvents(data["procedureEvents"]) if "intraOpTargets" in data.keys(): intraOpTargetsInfo = data["intraOpTargets"] if type(intraOpTargetsInfo) == type(dict()): self.intraOpTargets = self._loadOrGetFileData( directory, intraOpTargetsInfo["targetFile"], slicer.util.loadMarkupsFiducialList) self.savedNeedleTypeForTargets = intraOpTargetsInfo.get( "needleType") elif type(intraOpTargetsInfo) == type(unicode( )): # ensure backward compatibility to load old data self.intraOpTargets = self._loadOrGetFileData( directory, data["intraOpTargets"], slicer.util.loadMarkupsFiducialList) self.intraOpTargets.SetLocked(True) if "zFrameRegistration" in data.keys(): zFrameRegistration = data["zFrameRegistration"] volume = self._loadOrGetFileData(directory, zFrameRegistration["volume"], slicer.util.loadVolume) transform = self._loadOrGetFileData( directory, zFrameRegistration["transform"], slicer.util.loadTransform) name = zFrameRegistration["name"] if zFrameRegistration.has_key( "name") else volume.GetName() self.zFrameRegistrationResult = ZFrameRegistrationResult(name) self.zFrameRegistrationResult.volume = volume self.zFrameRegistrationResult.transform = transform if zFrameRegistration["seriesType"]: self.seriesTypeManager.assign( self.zFrameRegistrationResult.name, zFrameRegistration["seriesType"]) if "initialVolume" in data.keys(): self.initialVolume = self._loadOrGetFileData( directory, data["initialVolume"], slicer.util.loadVolume) if "tumorSegmentation" in data.keys(): self.segmentModelNode = self._loadOrGetFileData( directory, data["tumorSegmentation"], slicer.util.loadSegmentation) #self.loadResults(data, directory) ## ?? why need to load twice return True def _loadOrGetFileData(self, directory, filename, loadFunction): if not filename: return None try: data = self.alreadyLoadedFileNames[filename] except KeyError: _, data = loadFunction(os.path.join(directory, filename), returnNode=True) self.alreadyLoadedFileNames[filename] = data return data def generateLogfileTimeStampDict(self): return { "time": self.getTime(), "logfile": os.path.basename(self.getSlicerErrorLogPath()) } def close(self, outputDir): if not self.completed: self.closedLogTimeStamps.append( self.generateLogfileTimeStampDict()) return self.save(outputDir) def save(self, outputDir): if not os.path.exists(outputDir): self.createDirectory(outputDir) successfullySavedFileNames = [] failedSaveOfFileNames = [] logFilePath = self.getSlicerErrorLogPath() shutil.copy(logFilePath, os.path.join(outputDir, os.path.basename(logFilePath))) successfullySavedFileNames.append( os.path.join(outputDir, os.path.basename(logFilePath))) def saveManualSegmentation(): if self.segmentModelNode: if self.segmentModelNode.GetSegmentation().GetNumberOfSegments( ) > 0: success, name = self.saveNodeData(self.segmentModelNode, outputDir, ".seg.nrrd", overwrite=True) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) return name + ".seg.nrrd" return None def saveInitialVolume(): success, name = self.saveNodeData(self.initialVolume, outputDir, FileExtension.NRRD, overwrite=True) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) return name + FileExtension.NRRD data = {} def saveIntraOpTargets(): success, name = self.saveNodeData(self.intraOpTargets, outputDir, FileExtension.FCSV, name="IntraOpTargets", overwrite=True) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) intraOpTargetsInfo = {"targetFile": name + FileExtension.FCSV} intraOpTargetsInfo["needleType"] = self.savedNeedleTypeForTargets data["intraOpTargets"] = intraOpTargetsInfo return def addProcedureEvents(): procedureEvents = { "caseStarted": self.startTimeStamp, } if len(self.closedLogTimeStamps): procedureEvents["caseClosed"] = self.closedLogTimeStamps if len(self.resumeTimeStamps): procedureEvents["caseResumed"] = self.resumeTimeStamps if self.completed: procedureEvents["caseCompleted"] = self.completedLogTimeStamp data["procedureEvents"] = procedureEvents addProcedureEvents() if self.zFrameRegistrationResult: data["zFrameRegistration"] = self.zFrameRegistrationResult.save( outputDir) if self.intraOpTargets: saveIntraOpTargets() if self.initialVolume: data["initialVolume"] = saveInitialVolume() if self.segmentModelNode: data["tumorSegmentation"] = saveManualSegmentation() destinationFile = os.path.join(outputDir, self.DEFAULT_JSON_FILE_NAME) with open(destinationFile, 'w') as outfile: logging.debug("Writing registration results to %s" % destinationFile) json.dump(data, outfile, indent=2) self.printOutput("The following data was successfully saved:\n", successfullySavedFileNames) self.printOutput("The following data failed to saved:\n", failedSaveOfFileNames) return (len(failedSaveOfFileNames) == 0, failedSaveOfFileNames) def printOutput(self, message, fileNames): if not len(fileNames): return for fileName in fileNames: message += fileName + "\n" logging.debug(message)
class SliceTrackerSession(StepBasedSession): IncomingDataSkippedEvent = SlicerDevelopmentToolboxEvents.SkippedEvent IncomingIntraopDataReceiveFinishedEvent = SlicerDevelopmentToolboxEvents.FinishedEvent + 111 NewImageSeriesReceivedEvent = SlicerDevelopmentToolboxEvents.NewImageDataReceivedEvent ZFrameRegistrationSuccessfulEvent = vtk.vtkCommand.UserEvent + 140 PreprocessingSuccessfulEvent = vtk.vtkCommand.UserEvent + 141 LoadingMetadataSuccessfulEvent = vtk.vtkCommand.UserEvent + 143 SegmentationCancelledEvent = vtk.vtkCommand.UserEvent + 144 CurrentSeriesChangedEvent = vtk.vtkCommand.UserEvent + 151 CurrentResultChangedEvent = vtk.vtkCommand.UserEvent + 152 RegistrationStatusChangedEvent = vtk.vtkCommand.UserEvent + 153 TargetSelectionEvent = vtk.vtkCommand.UserEvent + 154 InitiateZFrameCalibrationEvent = vtk.vtkCommand.UserEvent + 160 InitiateSegmentationEvent = vtk.vtkCommand.UserEvent + 161 InitiateRegistrationEvent = vtk.vtkCommand.UserEvent + 162 InitiateEvaluationEvent = vtk.vtkCommand.UserEvent + 163 SeriesTypeManuallyAssignedEvent = SeriesTypeManager.SeriesTypeManuallyAssignedEvent MODULE_NAME = SliceTrackerConstants.MODULE_NAME @property def preprocessedDirectory(self): return os.path.join(self.directory, "mpReviewPreprocessed") if self.directory else None @property def preopDICOMDirectory(self): return os.path.join(self.directory, "DICOM", "Preop") if self.directory else None @property def intraopDICOMDirectory(self): return os.path.join(self.directory, "DICOM", "Intraop") if self.directory else None @property def outputDirectory(self): return os.path.join(self.directory, "SliceTrackerOutputs") @property def approvedCoverTemplate(self): try: return self.data.zFrameRegistrationResult.volume except AttributeError: return None @approvedCoverTemplate.setter def approvedCoverTemplate(self, volume): self.data.zFrameRegistrationResult.volume = volume self.zFrameRegistrationSuccessful = volume is not None @property def zFrameRegistrationSuccessful(self): self._zFrameRegistrationSuccessful = getattr( self, "_zFrameRegistrationSuccessful", None) return self.data.zFrameRegistrationResult is not None and self._zFrameRegistrationSuccessful @zFrameRegistrationSuccessful.setter def zFrameRegistrationSuccessful(self, value): self._zFrameRegistrationSuccessful = value if self._zFrameRegistrationSuccessful: self.save() self.invokeEvent(self.ZFrameRegistrationSuccessfulEvent) @property def currentResult(self): return self._getCurrentResult() @onExceptionReturnNone def _getCurrentResult(self): return self.data.registrationResults[self._currentResult] @currentResult.setter def currentResult(self, series): if self.currentResult and self.currentResult.name == series: return if self.currentResult is not None: self.currentResult.removeEventObservers() self._currentResult = series if self.currentResult: for event in RegistrationResult.StatusEvents.values(): self.currentResult.addEventObserver( event, self.onRegistrationResultStatusChanged) self.invokeEvent(self.CurrentResultChangedEvent) @property def currentSeries(self): self._currentSeries = getattr(self, "_currentSeries", None) return self._currentSeries @currentSeries.setter def currentSeries(self, series): if series == self.currentSeries: return if series and series not in self.seriesList: raise UnknownSeriesError("Series %s is unknown" % series) self._currentSeries = series self.invokeEvent(self.CurrentSeriesChangedEvent, series) @property def currentSeriesVolume(self): if not self.currentSeries: return None else: return self.getOrCreateVolumeForSeries(self.currentSeries) @property def movingVolume(self): self._movingVolume = getattr(self, "_movingVolume", None) return self._movingVolume @movingVolume.setter def movingVolume(self, value): self._movingVolume = value @property def movingLabel(self): self._movingLabel = getattr(self, "_movingLabel", None) return self._movingLabel @movingLabel.setter def movingLabel(self, value): self._movingLabel = value @property def movingTargets(self): self._movingTargets = getattr(self, "_movingTargets", None) if self.isCurrentSeriesCoverProstateInNonPreopMode(): return self.data.initialTargets return self._movingTargets @movingTargets.setter def movingTargets(self, value): if self.isCurrentSeriesCoverProstateInNonPreopMode(): self.data.initialTargets = value self._movingTargets = value @property def fixedVolume(self): self._fixedVolume = getattr(self, "_fixedVolume", None) if self.isCurrentSeriesCoverProstateInNonPreopMode(): return self.data.initialVolume return self._fixedVolume @fixedVolume.setter def fixedVolume(self, value): if self.isCurrentSeriesCoverProstateInNonPreopMode(): self.data.initialVolume = value self._fixedVolume = value @property def fixedLabel(self): self._fixedLabel = getattr(self, "_fixedLabel", None) if self.isCurrentSeriesCoverProstateInNonPreopMode(): return self.data.initialLabel return self._fixedLabel @fixedLabel.setter def fixedLabel(self, value): if self.isCurrentSeriesCoverProstateInNonPreopMode(): self.data.initialLabel = value self._fixedLabel = value def setSelectedTarget(self, info): self.invokeEvent(self.TargetSelectionEvent, str(info)) def __init__(self): StepBasedSession.__init__(self) self.registrationLogic = SliceTrackerRegistrationLogic() self.seriesTypeManager = SeriesTypeManager() self.seriesTypeManager.addEventObserver( self.seriesTypeManager.SeriesTypeManuallyAssignedEvent, lambda caller, event: self.invokeEvent( self.SeriesTypeManuallyAssignedEvent)) self.resetAndInitializeMembers() def resetAndInitializeMembers(self): self._busy = False self.seriesTypeManager.clear() self.initializeColorNodes() self.directory = None self.data = SessionData() self.data.addEventObserver(self.data.NewResultCreatedEvent, self.onNewRegistrationResultCreated) self.trainingMode = False self.resetPreopDICOMReceiver() self.resetIntraopDICOMReceiver() self.loadableList = {} self.seriesList = [] self.seriesTimeStamps = dict() self.alreadyLoadedSeries = {} self._currentResult = None self._currentSeries = None self.retryMode = False self.previousStep = None self.temporaryIntraopTargets = None def initializeColorNodes(self): from mpReview import mpReviewLogic self.mpReviewColorNode, self.structureNames = mpReviewLogic.loadColorTable( self.getSetting("Color_File_Name")) self.segmentedColorName = self.getSetting("Segmentation_Color_Name") self.segmentedLabelValue = self.mpReviewColorNode.GetColorIndexByName( self.segmentedColorName) def __del__(self): super(SliceTrackerSession, self).__del__() self.clearData() def clearData(self): self.resetAndInitializeMembers() def onMrmlSceneCleared(self, caller, event): self.initializeColorNodes() @onExceptionReturnFalse def isCurrentSeriesCoverProstateInNonPreopMode(self): return self.seriesTypeManager.isCoverProstate( self.currentSeries) and not self.data.usePreopData def isBusy(self): return self.isPreProcessing() or self._busy def isPreProcessing(self): return slicer.util.selectedModule() != self.MODULE_NAME def isCaseDirectoryValid(self): return all( os.path.exists(p) for p in [self.preopDICOMDirectory, self.intraopDICOMDirectory]) def isRunning(self): return not self.directory in [None, ''] def processDirectory(self): self.newCaseCreated = getattr(self, "newCaseCreated", False) if self.newCaseCreated: return if self.isCaseDirectoryValid(): self.loadCaseData() self.invokeEvent(self.CaseOpenedEvent) else: slicer.util.warningDisplay( "The selected case directory seems not to be valid", windowTitle="SliceTracker") self.close(save=False) def createNewCase(self, destination): self.newCaseCreated = True self.resetAndInitializeMembers() self.directory = destination self.createDirectory(self.preopDICOMDirectory) self.createDirectory(self.intraopDICOMDirectory) self.createDirectory(self.preprocessedDirectory) self.createDirectory(self.outputDirectory) self.startPreopDICOMReceiver() self.newCaseCreated = False self.invokeEvent(self.NewCaseStartedEvent) def close(self, save=False): if not self.isRunning(): return message = None if save: success, failedFileNames = self.data.close(self.outputDirectory) message = "Case data has been saved successfully." if success else \ "The following data failed to saved:\n %s" % failedFileNames self.resetAndInitializeMembers() self.invokeEvent(self.CloseCaseEvent, str(message)) def save(self): success, failedFileNames = self.data.save(self.outputDirectory) return success and not len( failedFileNames ), "The following data failed to saved:\n %s" % failedFileNames def complete(self): self.data.completed = True self.close(save=True) def load(self): filename = os.path.join(self.outputDirectory, SliceTrackerConstants.JSON_FILENAME) completed = self.data.wasSessionCompleted(filename) if slicer.util.confirmYesNoDisplay("A %s session has been found for the selected case. Do you want to %s?" \ % ("completed" if completed else "started", "open it" if completed else "continue this session")): slicer.app.layoutManager().blockSignals(True) self._busy = True self.data.load(filename) self.postProcessLoadedSessionData() slicer.app.layoutManager().blockSignals(False) self.invokeEvent(self.LoadingMetadataSuccessfulEvent) else: self.clearData() def postProcessLoadedSessionData(self): coverProstate = self.data.getMostRecentApprovedCoverProstateRegistration( ) if coverProstate: if not self.data.initialVolume: self.data.initialVolume = coverProstate.volumes.moving if self.data.usePreopData else coverProstate.volumes.fixed self.data.initialTargets = coverProstate.targets.original if self.data.usePreopData: # TODO: makes sense? self.data.preopLabel = coverProstate.labels.moving if self.data.zFrameRegistrationResult: self._zFrameRegistrationSuccessful = True self.data.resumed = not self.data.completed if self.data.usePreopData: preopDataManager = self.createPreopHandler() preopDataManager.loadPreProcessedData() else: if self.data.initialTargets: self.setupPreopLoadedTargets() self.startIntraopDICOMReceiver() def createPreopHandler(self): preopDataManager = PreopDataHandler(self.preopDICOMDirectory, self.preprocessedDirectory, self.data) preopDataManager.addEventObserver( preopDataManager.PreprocessedDataErrorEvent, lambda caller, event: self.close(save=False)) preopDataManager.addEventObserver( preopDataManager.PreprocessingStartedEvent, self.onPreprocessingStarted) preopDataManager.addEventObserver( preopDataManager.PreprocessingFinishedEvent, self.onPreprocessingSuccessful) return preopDataManager def startPreopDICOMReceiver(self): self.resetPreopDICOMReceiver() self.preopDICOMReceiver = IncomingDataWindow( incomingDataDirectory=self.preopDICOMDirectory, incomingPort=self.getSetting("Incoming_DICOM_Port"), skipText="No preoperative images available") self.preopDICOMReceiver.addEventObserver( SlicerDevelopmentToolboxEvents.SkippedEvent, self.onSkippingPreopDataReception) self.preopDICOMReceiver.addEventObserver( SlicerDevelopmentToolboxEvents.CanceledEvent, lambda caller, event: self.close()) self.preopDICOMReceiver.addEventObserver( SlicerDevelopmentToolboxEvents.FinishedEvent, self.onPreopDataReceptionFinished) self.preopDICOMReceiver.show() def onSkippingPreopDataReception(self, caller, event): self.data.usePreopData = False self.startIntraopDICOMReceiver() self.invokeEvent(self.IncomingDataSkippedEvent) def onPreopDataReceptionFinished(self, caller=None, event=None): self.data.usePreopData = True preopDataManager = self.createPreopHandler() preopDataManager.handle() def onPreprocessingStarted(self, caller, event): self._busy = True self.startIntraopDICOMReceiver() def resetPreopDICOMReceiver(self): self.preopDICOMReceiver = getattr(self, "preopDICOMReceiver", None) if self.preopDICOMReceiver: self.preopDICOMReceiver.hide() self.preopDICOMReceiver.removeEventObservers() self.preopDICOMReceiver = None def startIntraopDICOMReceiver(self): self.resetPreopDICOMReceiver() logging.debug("Starting DICOM Receiver for intra-procedural data") if not self.data.completed: self.resetIntraopDICOMReceiver() self.intraopDICOMReceiver = SmartDICOMReceiver( self.intraopDICOMDirectory, self.getSetting("Incoming_DICOM_Port")) self._observeIntraopDICOMReceiverEvents() self.intraopDICOMReceiver.start(not ( self.trainingMode or self.data.completed)) else: self.invokeEvent(SlicerDevelopmentToolboxEvents.StoppedEvent) self.importDICOMSeries(self.getFileList(self.intraopDICOMDirectory)) if self.intraopDICOMReceiver: self.intraopDICOMReceiver.forceStatusChangeEventUpdate() def resetIntraopDICOMReceiver(self): self.intraopDICOMReceiver = getattr(self, "intraopDICOMReceiver", None) if self.intraopDICOMReceiver: self.intraopDICOMReceiver.stop() self.intraopDICOMReceiver.removeEventObservers() def _observeIntraopDICOMReceiverEvents(self): self.intraopDICOMReceiver.addEventObserver( self.intraopDICOMReceiver.IncomingDataReceiveFinishedEvent, self.onDICOMSeriesReceived) self.intraopDICOMReceiver.addEventObserver( SlicerDevelopmentToolboxEvents.StatusChangedEvent, self.onDICOMReceiverStatusChanged) @vtk.calldata_type(vtk.VTK_STRING) def onDICOMSeriesReceived(self, caller, event, callData): self.importDICOMSeries(ast.literal_eval(callData)) if self.trainingMode is True: self.resetIntraopDICOMReceiver() @vtk.calldata_type(vtk.VTK_STRING) def onDICOMReceiverStatusChanged(self, caller, event, callData): customStatusProgressBar = CustomStatusProgressbar() customStatusProgressBar.text = callData customStatusProgressBar.busy = "Waiting" in callData def importDICOMSeries(self, newFileList): indexer = ctk.ctkDICOMIndexer() newSeries = [] for currentIndex, currentFile in enumerate(newFileList, start=1): self.invokeEvent( SlicerDevelopmentToolboxEvents.NewFileIndexedEvent, [ "Indexing file %s" % currentFile, len(newFileList), currentIndex ].__str__()) slicer.app.processEvents() currentFile = os.path.join(self.intraopDICOMDirectory, currentFile) indexer.addFile(slicer.dicomDatabase, currentFile, None) series = self.makeSeriesNumberDescription(currentFile) if series not in self.seriesList: self.seriesTimeStamps[series] = self.getTime() self.seriesList.append(series) newSeries.append(series) self.loadableList[ series] = self.createLoadableFileListForSeries(series) self.seriesList = sorted( self.seriesList, key=lambda s: RegistrationResult.getSeriesNumberFromString(s)) if len(newFileList): self.verifyPatientIDEquality(newFileList) self.invokeEvent(self.NewImageSeriesReceivedEvent, newSeries.__str__()) def verifyPatientIDEquality(self, receivedFiles): seriesNumberPatientID = self.getAdditionalInformationForReceivedSeries( receivedFiles) dicomFileName = self.getPatientIDValidationSource() if not dicomFileName: return currentInfo = self.getPatientInformation(dicomFileName) currentID = currentInfo["PatientID"] patientName = currentInfo["PatientName"] for seriesNumber, receivedInfo in seriesNumberPatientID.iteritems(): patientID = receivedInfo["PatientID"] if patientID is not None and patientID != currentID: m = 'WARNING:\n' \ 'Current case:\n' \ ' Patient ID: {0}\n' \ ' Patient Name: {1}\n' \ 'Received image\n' \ ' Patient ID: {2}\n' \ ' Patient Name : {3}\n\n' \ 'Do you want to keep this series? '.format(currentID, patientName, patientID, receivedInfo["PatientName"]) if not slicer.util.confirmYesNoDisplay( m, title="Patient IDs Not Matching", windowTitle="SliceTracker"): self.deleteSeriesFromSeriesList(seriesNumber) def getPatientIDValidationSource(self): if len(self.loadableList.keys()) > 1: keys = self.loadableList.keys() keys.sort( key=lambda x: RegistrationResult.getSeriesNumberFromString(x)) return self.loadableList[keys[0]][0] else: return None def getOrCreateVolumeForSeries(self, series): try: volume = self.alreadyLoadedSeries[series] except KeyError: files = self.loadableList[series] loadables = self.scalarVolumePlugin.examine([files]) assert len(loadables) volume = self.scalarVolumePlugin.load(loadables[0]) volume.SetName(loadables[0].name) self.alreadyLoadedSeries[series] = volume slicer.app.processEvents() return volume def createLoadableFileListForSeries(self, series): seriesNumber = RegistrationResult.getSeriesNumberFromString(series) loadableList = [] for dcm in self.getFileList(self.intraopDICOMDirectory): currentFile = os.path.join(self.intraopDICOMDirectory, dcm) currentSeriesNumber = int( self.getDICOMValue(currentFile, DICOMTAGS.SERIES_NUMBER)) if currentSeriesNumber and currentSeriesNumber == seriesNumber: loadableList.append(currentFile) return loadableList def deleteSeriesFromSeriesList(self, seriesNumber): for series in self.seriesList: currentSeriesNumber = RegistrationResult.getSeriesNumberFromString( series) if currentSeriesNumber == seriesNumber: self.seriesList.remove(series) for seriesFile in self.loadableList[series]: logging.debug( "removing {} from filesystem".format(seriesFile)) os.remove(seriesFile) del self.loadableList[series] def makeSeriesNumberDescription(self, dcmFile): seriesDescription = self.getDICOMValue(dcmFile, DICOMTAGS.SERIES_DESCRIPTION) seriesNumber = self.getDICOMValue(dcmFile, DICOMTAGS.SERIES_NUMBER) if not (seriesNumber and seriesDescription): raise DICOMValueError( "Missing Attribute(s):\nFile: {}\nseriesNumber: {}\nseriesDescription: {}" .format(dcmFile, seriesNumber, seriesDescription)) return "{}: {}".format(seriesNumber, seriesDescription) def getAdditionalInformationForReceivedSeries(self, fileList): seriesNumberPatientID = {} for currentFile in [ os.path.join(self.intraopDICOMDirectory, f) for f in fileList ]: seriesNumber = int( self.getDICOMValue(currentFile, DICOMTAGS.SERIES_NUMBER)) if seriesNumber not in seriesNumberPatientID.keys(): seriesNumberPatientID[ seriesNumber] = self.getPatientInformation(currentFile) return seriesNumberPatientID def getPatientInformation(self, currentFile): return { "PatientID": self.getDICOMValue(currentFile, DICOMTAGS.PATIENT_ID), "PatientName": self.getDICOMValue(currentFile, DICOMTAGS.PATIENT_NAME), "SeriesDescription": self.getDICOMValue(currentFile, DICOMTAGS.SERIES_DESCRIPTION) } def getSeriesForSubstring(self, substring): for series in reversed(self.seriesList): if substring in series: return series return None def loadCaseData(self): self._busy = True if self.hasJSONResults(): self.load() else: if PreopDataHandler.wasDirectoryPreprocessed( self.preprocessedDirectory): self.startIntraopDICOMReceiver() preopDataManager = self.createPreopHandler() preopDataManager.loadPreProcessedData() else: self.continueWithUnprocessedData() def continueWithUnprocessedData(self): if os.listdir(self.preopDICOMDirectory): self.onPreopDataReceptionFinished() elif os.listdir(self.intraopDICOMDirectory): self.data.usePreopData = False self.startIntraopDICOMReceiver() else: self.startPreopDICOMReceiver() def hasJSONResults(self): return os.path.exists( os.path.join(self.outputDirectory, SliceTrackerConstants.JSON_FILENAME)) def onPreprocessingSuccessful(self, caller, event): self.setupPreopLoadedTargets() self._busy = False self.invokeEvent(self.PreprocessingSuccessfulEvent) self.startIntraopDICOMReceiver() def setupPreopLoadedTargets(self): targets = self.data.initialTargets ModuleWidgetMixin.setFiducialNodeVisibility(targets, show=True) self.applyDefaultTargetDisplayNode(targets) self.markupsLogic.JumpSlicesToNthPointInMarkup(targets.GetID(), 0) def applyDefaultTargetDisplayNode(self, targetNode, new=False): displayNode = None if new else targetNode.GetDisplayNode() modifiedDisplayNode = self.setupDisplayNode(displayNode, True) targetNode.SetAndObserveDisplayNodeID(modifiedDisplayNode.GetID()) def setupDisplayNode(self, displayNode=None, starBurst=False): if not displayNode: displayNode = slicer.vtkMRMLMarkupsDisplayNode() slicer.mrmlScene.AddNode(displayNode) displayNode.SetTextScale(0) displayNode.SetGlyphScale(2.5) if starBurst: displayNode.SetGlyphType( slicer.vtkMRMLAnnotationPointDisplayNode.StarBurst2D) return displayNode def getColorForSelectedSeries(self, series=None): series = series if series else self.currentSeries if series in [None, '']: return STYLE.WHITE_BACKGROUND style = STYLE.YELLOW_BACKGROUND if not self.isTrackingPossible(series): if self.data.registrationResultWasApproved(series) or \ (self.zFrameRegistrationSuccessful and self.seriesTypeManager.isCoverTemplate(series)): style = STYLE.GREEN_BACKGROUND elif self.data.registrationResultWasSkipped(series): style = STYLE.RED_BACKGROUND elif self.data.registrationResultWasRejected(series): style = STYLE.GRAY_BACKGROUND return style def isTrackingPossible(self, series): if self.data.completed: logging.debug( "No tracking possible. Case has been marked as completed!") return False if self.isInGeneralTrackable( series) and self.resultHasNotBeenProcessed(series): if self.seriesTypeManager.isGuidance(series): return self.data.getMostRecentApprovedCoverProstateRegistration( ) elif self.seriesTypeManager.isCoverProstate(series): return self.zFrameRegistrationSuccessful elif self.isCoverTemplateTrackable(series): return True return False def isCoverTemplateTrackable(self, series): if not self.seriesTypeManager.isCoverTemplate(series): return False if not self.approvedCoverTemplate: return True currentSeriesNumber = RegistrationResult.getSeriesNumberFromString( series) vName = self.approvedCoverTemplate.GetName() approvedSeriesNumber = int( vName.split(":" if vName.find(":") > -1 else "-")[0]) return currentSeriesNumber > approvedSeriesNumber def isInGeneralTrackable(self, series): seriesType = self.seriesTypeManager.getSeriesType(series) return self.isAnyListItemInString(seriesType, [ self.getSetting("COVER_TEMPLATE_PATTERN"), self.getSetting("COVER_PROSTATE_PATTERN"), self.getSetting("NEEDLE_IMAGE_PATTERN") ]) def resultHasNotBeenProcessed(self, series): return not (self.data.registrationResultWasApproved(series) or self.data.registrationResultWasSkipped(series) or self.data.registrationResultWasRejected(series)) def isEligibleForSkipping(self, series): seriesType = self.seriesTypeManager.getSeriesType(series) return not self.isAnyListItemInString(seriesType, [ self.getSetting("COVER_PROSTATE_PATTERN"), self.getSetting("COVER_TEMPLATE_PATTERN") ]) def takeActionForCurrentSeries(self): event = None callData = None if self.seriesTypeManager.isCoverProstate(self.currentSeries): event = self.InitiateSegmentationEvent callData = str(False) elif self.seriesTypeManager.isCoverTemplate(self.currentSeries): event = self.InitiateZFrameCalibrationEvent elif self.seriesTypeManager.isGuidance(self.currentSeries): self.onInvokeRegistration(initial=False) return if event: self.invokeEvent(event, callData) else: raise UnknownSeriesError( "Action for currently selected series unknown") def retryRegistration(self): self.invokeEvent(self.InitiateSegmentationEvent, str(True)) def getRegistrationResultNameAndGeneratedSuffix(self, name): nOccurrences = sum([ 1 for result in self.data.getResultsAsList() if name in result.name ]) suffix = "" if nOccurrences: suffix = "_Retry_" + str(nOccurrences) return name, suffix def onInvokeRegistration(self, initial=True, retryMode=False, segmentationData=None): self.progress = ModuleWidgetMixin.createProgressDialog( maximum=4, value=1, windowFlags=qt.Qt.CustomizeWindowHint | qt.Qt.WindowTitleHint) self.progress.setCancelButton(None) if initial: self.applyInitialRegistration( retryMode, segmentationData, progressCallback=self.updateProgressBar) else: self.applyRegistration(progressCallback=self.updateProgressBar) self.progress.close() self.progress = None logging.debug('Re-Registration is done') @onReturnProcessEvents def updateProgressBar(self, **kwargs): if self.progress: for key, value in kwargs.iteritems(): if hasattr(self.progress, key): setattr(self.progress, key, value) def generateNameAndCreateRegistrationResult(self, fixedVolume): name, suffix = self.getRegistrationResultNameAndGeneratedSuffix( fixedVolume.GetName()) result = self.data.createResult(name + suffix) result.suffix = suffix self.registrationLogic.registrationResult = result return result def applyInitialRegistration(self, retryMode, segmentationData, progressCallback=None): if not retryMode: self.data.initializeRegistrationResults() self.runBRAINSResample(inputVolume=self.fixedLabel, referenceVolume=self.fixedVolume, outputVolume=self.fixedLabel) self._runRegistration(self.fixedVolume, self.fixedLabel, self.movingVolume, self.movingLabel, self.movingTargets, segmentationData, progressCallback) def applyRegistration(self, progressCallback=None): coverProstateRegResult = self.data.getMostRecentApprovedCoverProstateRegistration( ) lastRigidTfm = self.data.getLastApprovedRigidTransformation() lastApprovedTfm = self.data.getMostRecentApprovedTransform() initialTransform = lastApprovedTfm if lastApprovedTfm else lastRigidTfm fixedLabel = self.volumesLogic.CreateAndAddLabelVolume( slicer.mrmlScene, self.currentSeriesVolume, self.currentSeriesVolume.GetName() + '-label') self.runBRAINSResample(inputVolume=coverProstateRegResult.labels.fixed, referenceVolume=self.currentSeriesVolume, outputVolume=fixedLabel, warpTransform=initialTransform) self.dilateMask(fixedLabel, dilateValue=self.segmentedLabelValue) self._runRegistration(self.currentSeriesVolume, fixedLabel, coverProstateRegResult.volumes.fixed, coverProstateRegResult.labels.fixed, coverProstateRegResult.targets.approved, None, progressCallback) def _runRegistration(self, fixedVolume, fixedLabel, movingVolume, movingLabel, targets, segmentationData, progressCallback): result = self.generateNameAndCreateRegistrationResult(fixedVolume) result.receivedTime = self.seriesTimeStamps[result.name.replace( result.suffix, "")] if segmentationData: result.segmentationData = segmentationData parameterNode = slicer.vtkMRMLScriptedModuleNode() parameterNode.SetAttribute('FixedImageNodeID', fixedVolume.GetID()) parameterNode.SetAttribute('FixedLabelNodeID', fixedLabel.GetID()) parameterNode.SetAttribute('MovingImageNodeID', movingVolume.GetID()) parameterNode.SetAttribute('MovingLabelNodeID', movingLabel.GetID()) parameterNode.SetAttribute('TargetsNodeID', targets.GetID()) result.startTime = self.getTime() self.registrationLogic.run(parameterNode, progressCallback=progressCallback) result.endTime = self.getTime() self.addTargetsToMRMLScene(result) if self.seriesTypeManager.isCoverProstate( self.currentSeries) and self.temporaryIntraopTargets: self.addTemporaryTargetsToResult(result) self.invokeEvent(self.InitiateEvaluationEvent) def addTargetsToMRMLScene(self, result): targetNodes = result.targets.asDict() for regType in RegistrationTypeData.RegistrationTypes: if targetNodes[regType]: slicer.mrmlScene.AddNode(targetNodes[regType]) def addTemporaryTargetsToResult(self, result): length = self.temporaryIntraopTargets.GetNumberOfFiducials() targetNodes = result.targets.asDict() for targetList in [ targetNodes[r] for r in RegistrationTypeData.RegistrationTypes if targetNodes[r] ]: for i in range(length): targetList.AddFiducialFromArray( self.getTargetPosition(self.temporaryIntraopTargets, i), self.temporaryIntraopTargets.GetNthFiducialLabel(i)) def onRegistrationResultStatusChanged(self, caller, event): self.skipAllUnregisteredPreviousSeries(self.currentResult.name) if self._busy: return self.save() self.invokeEvent(self.RegistrationStatusChangedEvent) @vtk.calldata_type(vtk.VTK_STRING) def onNewRegistrationResultCreated(self, caller, event, callData): self.currentResult = callData def skipAllUnregisteredPreviousSeries(self, series): selectedSeriesNumber = RegistrationResult.getSeriesNumberFromString( series) for series in [ series for series in self.seriesList if not self.seriesTypeManager.isCoverTemplate(series) ]: currentSeriesNumber = RegistrationResult.getSeriesNumberFromString( series) if currentSeriesNumber < selectedSeriesNumber and self.isTrackingPossible( series): results = self.data.getResultsBySeriesNumber( currentSeriesNumber) if len(results) == 0: self.skipSeries(series) elif currentSeriesNumber >= selectedSeriesNumber: break def skipSeries(self, series): volume = self.getOrCreateVolumeForSeries(series) name, suffix = self.getRegistrationResultNameAndGeneratedSuffix( volume.GetName()) result = self.data.createResult(name + suffix) result.volumes.fixed = volume result.receivedTime = self.seriesTimeStamps[result.name.replace( result.suffix, "")] result.skip() def skip(self, series): self.skipAllUnregisteredPreviousSeries(series) self.skipSeries(series) self.save() def _getConsent(self): return RadioButtonChoiceMessageBox("Who gave consent?", options=["Clinician", "Operator"]).exec_()
def seriesType(self): seriesTypeManager = SeriesTypeManager() return seriesTypeManager.getSeriesType(self.name)
def getMostRecentApprovedCoverProstateRegistration(self): seriesTypeManager = SeriesTypeManager() for result in self.registrationResults.values(): if seriesTypeManager.isCoverProstate(result.name) and result.approved: return result return None
class SessionData(ModuleLogicMixin): NewResultCreatedEvent = vtk.vtkCommand.UserEvent + 901 _completed = False _resumed = False @property def completed(self): return self._completed @completed.setter def completed(self, value): self._completed = value self.completedLogTimeStamp = self.generateLogfileTimeStampDict() if self._completed else None @property def resumed(self): return self._resumed @resumed.setter def resumed(self, value): if value and self.completed: raise ValueError("Completed case is not supposed to be resumed.") if value and not self.completed: self.resumeTimeStamps.append(self.getTime()) self._resumed = value @staticmethod def wasSessionCompleted(filename): with open(filename) as data_file: data = json.load(data_file) procedureEvents = data["procedureEvents"] return "caseCompleted" in procedureEvents.keys() @property def usedAutomaticPreopSegmentation(self): return self.preopData is not None and self.preopData.segmentation.algorithm == "Automatic" @property def usePreopData(self): self.preopData = getattr(self, "preopData", None) return self.preopData is not None @usePreopData.setter def usePreopData(self, value): self._usePreopData = value self.preopData = PreopData() if self._usePreopData else None def __init__(self): self.resetAndInitializeData() def resetAndInitializeData(self): self.seriesTypeManager = SeriesTypeManager() self.startTime = self.getTime() self.resumeTimeStamps = [] self.closedLogTimeStamps = [] self.completed = False self.usePreopData = False self.segmentModelNode = None self.inputMarkupNode = None self.initialVolume = None self.initialLabel = None self.initialTargets = None self.initialTargetsPath = None self.zFrameRegistrationResult = None self._savedRegistrationResults = [] self.initializeRegistrationResults() self.customProgressBar = CustomStatusProgressbar() self.alreadyLoadedFileNames = {} def initializeRegistrationResults(self): self.registrationResults = OrderedDict() def createZFrameRegistrationResult(self, series): self.zFrameRegistrationResult = ZFrameRegistrationResult(series) return self.zFrameRegistrationResult def createResult(self, series, invokeEvent=True): assert series not in self.registrationResults.keys() self.registrationResults[series] = RegistrationResult(series) if invokeEvent is True: self.invokeEvent(self.NewResultCreatedEvent, series) return self.registrationResults[series] def load(self, filename): directory = os.path.dirname(filename) self.resetAndInitializeData() with open(filename) as dataFile: self.customProgressBar.visible = True self.customProgressBar.text = "Reading meta information" logging.debug("reading json file %s" % filename) data = json.load(dataFile) self.readInitialTargetsAndVolume(data, directory) self.loadZFrameRegistrationData(data, directory) self.loadProcedureEvents(data) self.loadPreopData(data) self.loadResults(data, directory) self.registrationResults = OrderedDict(sorted(self.registrationResults.items())) return True def readInitialTargetsAndVolume(self, data, directory): if "initialTargets" in data.keys(): self.initialTargets = self._loadOrGetFileData(directory, data["initialTargets"], slicer.util.loadMarkupsFiducialList) self.initialTargets.SetLocked(True) self.initialTargetsPath = os.path.join(directory, data["initialTargets"]) if "initialVolume" in data.keys(): self.initialVolume = self._loadOrGetFileData(directory, data["initialVolume"], slicer.util.loadVolume) def loadZFrameRegistrationData(self, data, directory): if "zFrameRegistration" in data.keys(): zFrameRegistration = data["zFrameRegistration"] volume = self._loadOrGetFileData(directory, zFrameRegistration["volume"], slicer.util.loadVolume) transform = self._loadOrGetFileData(directory, zFrameRegistration["transform"], slicer.util.loadTransform) name = zFrameRegistration["name"] if zFrameRegistration.has_key("name") else volume.GetName() self.zFrameRegistrationResult = ZFrameRegistrationResult(name) self.zFrameRegistrationResult.volume = volume self.zFrameRegistrationResult.transform = transform if zFrameRegistration["seriesType"]: self.seriesTypeManager.assign(self.zFrameRegistrationResult.name, zFrameRegistration["seriesType"]) def loadProcedureEvents(self, data): procedureEvents = data["procedureEvents"] self.startTime = procedureEvents["caseStarted"] self.completed = "caseCompleted" in procedureEvents.keys() if self.completed: self.completedLogTimeStamp = procedureEvents["caseCompleted"] if "caseClosed" in procedureEvents.keys(): self.closedLogTimeStamps = procedureEvents["caseClosed"] if "caseResumed" in procedureEvents.keys(): self.resumeTimeStamps = procedureEvents["caseResumed"] def loadPreopData(self, data): if data.has_key("preop"): self.preopData = PreopData.createFromJSON(data["preop"]) def loadResults(self, data, directory): if len(data["results"]): self.customProgressBar.maximum = len(data["results"]) for index, jsonResult in enumerate(data["results"], start=1): name = jsonResult["name"] logging.debug("processing %s" % name) result = self.createResult(name, invokeEvent=False) self.customProgressBar.updateStatus("Loading series registration result %s" % result.name, index) slicer.app.processEvents() for attribute, value in jsonResult.iteritems(): logging.debug("found %s: %s" % (attribute, value)) if attribute == 'volumes': self._loadResultFileData(value, directory, slicer.util.loadVolume, result.setVolume) elif attribute == 'transforms': self._loadResultFileData(value, directory, slicer.util.loadTransform, result.setTransform) elif attribute == 'targets': approved = value.pop('approved', None) original = value.pop('original', None) self._loadResultFileData(value, directory, slicer.util.loadMarkupsFiducialList, result.setTargets) if approved: approvedTargets = self._loadOrGetFileData(directory, approved["fileName"], slicer.util.loadMarkupsFiducialList) setattr(result.targets, 'approved', approvedTargets) result.targets.modifiedTargets[jsonResult["status"]["registrationType"]] = approved["userModified"] if original: originalTargets = self._loadOrGetFileData(directory, original, slicer.util.loadMarkupsFiducialList) setattr(result.targets, 'original', originalTargets) elif attribute == 'labels': self._loadResultFileData(value, directory, slicer.util.loadLabelVolume, result.setLabel) elif attribute == 'status': result.status = value["state"] result.timestamp = value["time"] result.registrationType = value["registrationType"] if value.has_key("registrationType") else None result.consentGivenBy = value["consentGivenBy"] if value.has_key("consentGivenBy") else None elif attribute == 'series': result.receivedTime = value['receivedTime'] seriesType = value["type"] if value.has_key("type") else None self.seriesTypeManager.assign(name, seriesType) elif attribute == 'registration': result.startTime = value['startTime'] result.endTime = value['endTime'] elif attribute == 'segmentation': result.segmentationData = SegmentationData.createFromJSON(value) else: setattr(result, attribute, value) self.customProgressBar.text = "Finished loading registration results" def _loadResultFileData(self, dictionary, directory, loadFunction, setFunction): for regType, filename in dictionary.iteritems(): data = self._loadOrGetFileData(directory, filename, loadFunction) setFunction(regType, data) def _loadOrGetFileData(self, directory, filename, loadFunction): if not filename: return None try: data = self.alreadyLoadedFileNames[filename] except KeyError: _, data = loadFunction(os.path.join(directory, filename), returnNode=True) self.alreadyLoadedFileNames[filename] = data return data def generateLogfileTimeStampDict(self): return { "time": self.getTime(), "logfile": os.path.basename(self.getSlicerErrorLogPath()) } def close(self, outputDir): if not self.completed: self.closedLogTimeStamps.append(self.generateLogfileTimeStampDict()) return self.save(outputDir) def save(self, outputDir): if not os.path.exists(outputDir): self.createDirectory(outputDir) successfullySavedFileNames = [] failedSaveOfFileNames = [] logFilePath = self.getSlicerErrorLogPath() shutil.copy(logFilePath, os.path.join(outputDir, os.path.basename(logFilePath))) successfullySavedFileNames.append(os.path.join(outputDir, os.path.basename(logFilePath))) def saveManualSegmentation(): if self.segmentModelNode: success, name = self.saveNodeData(self.segmentModelNode, outputDir, FileExtension.VTK) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) if self.inputMarkupNode: success, name = self.saveNodeData(self.inputMarkupNode, outputDir, FileExtension.FCSV) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) def saveInitialTargets(): success, name = self.saveNodeData(self.initialTargets, outputDir, FileExtension.FCSV, name="Initial_Targets") self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) return name + FileExtension.FCSV def saveInitialVolume(): success, name = self.saveNodeData(self.initialVolume, outputDir, FileExtension.NRRD) self.handleSaveNodeDataReturn(success, name, successfullySavedFileNames, failedSaveOfFileNames) return name + FileExtension.NRRD def createResultsList(): results = [] for result in sorted(self.getResultsAsList(), key=lambda r: r.seriesNumber): results.append(result.asDict()) return results saveManualSegmentation() data = { "results": createResultsList() } if self.preopData: self.preopData.save(outputDir) data["preop"] = self.preopData.toJSON() data.update(self.getGITRevisionInformation()) def addProcedureEvents(): procedureEvents = { "caseStarted": self.startTime, } if len(self.closedLogTimeStamps): procedureEvents["caseClosed"] = self.closedLogTimeStamps if len(self.resumeTimeStamps): procedureEvents["caseResumed"] = self.resumeTimeStamps if self.completed: procedureEvents["caseCompleted"] = self.completedLogTimeStamp data["procedureEvents"] = procedureEvents addProcedureEvents() if self.zFrameRegistrationResult: data["zFrameRegistration"] = self.zFrameRegistrationResult.save(outputDir) if self.initialTargets: data["initialTargets"] = saveInitialTargets() if self.initialVolume: data["initialVolume"] = saveInitialVolume() destinationFile = os.path.join(outputDir, SliceTrackerConstants.JSON_FILENAME) with open(destinationFile, 'w') as outfile: logging.debug("Writing registration results to %s" % destinationFile) json.dump(data, outfile, indent=2) failedSaveOfFileNames += self.saveRegistrationResults(outputDir) self.printOutput("The following data was successfully saved:\n", successfullySavedFileNames) self.printOutput("The following data failed to saved:\n", failedSaveOfFileNames) return len(failedSaveOfFileNames) == 0, failedSaveOfFileNames def getGITRevisionInformation(self): import inspect dirname = os.path.dirname(inspect.getfile(self.__class__)) def getLocalGITRevisionInformation(): try: from git import Repo, InvalidGitRepositoryError repo = Repo(dirname, search_parent_directories=True) branch = repo.active_branch print branch.name return { "GIT_WC_URL": repo.remote().url, "GIT_COMMIT_HASH": repo.head.object.hexsha } except (ImportError, InvalidGitRepositoryError): return {} filename = os.path.join(dirname,"..", "Resources/version.json" ) with open(filename) as data_file: logging.debug("reading version json file %s" % filename) data = json.load(data_file) if not data["GIT_COMMIT_HASH"]: data = getLocalGITRevisionInformation() return data def printOutput(self, message, fileNames): if not len(fileNames): return for fileName in fileNames: message += fileName + "\n" logging.debug(message) @logmethod(level=logging.DEBUG) def saveRegistrationResults(self, outputDir): failedToSave = [] self.customProgressBar.visible = True for index, result in enumerate(self.getResultsAsList(), start=1): self.customProgressBar.maximum = len(self.registrationResults) self.customProgressBar.updateStatus("Saving registration result for series %s" % result.name, index) slicer.app.processEvents() if result not in self._savedRegistrationResults: successfulList, failedList = result.save(outputDir) failedToSave += failedList self._savedRegistrationResults.append(result) self.customProgressBar.text = "Registration data successfully saved" if len(failedToSave) == 0 else "Error/s occurred during saving" return failedToSave def _registrationResultHasStatus(self, series, status, method=all): if not type(series) is int: series = RegistrationResult.getSeriesNumberFromString(series) results = self.getResultsBySeriesNumber(series) return method(result.status == status for result in results) if len(results) else False def registrationResultWasApproved(self, series): return self._registrationResultHasStatus(series, RegistrationStatus.APPROVED_STATUS, method=any) def registrationResultWasSkipped(self, series): return self._registrationResultHasStatus(series, RegistrationStatus.SKIPPED_STATUS) def registrationResultWasRejected(self, series): return self._registrationResultHasStatus(series, RegistrationStatus.REJECTED_STATUS) def registrationResultWasApprovedOrRejected(self, series): return self._registrationResultHasStatus(series, RegistrationStatus.REJECTED_STATUS) or \ self._registrationResultHasStatus(series, RegistrationStatus.APPROVED_STATUS) def getResultsAsList(self): return self.registrationResults.values() def getMostRecentApprovedCoverProstateRegistration(self): seriesTypeManager = SeriesTypeManager() for result in self.registrationResults.values(): if seriesTypeManager.isCoverProstate(result.name) and result.approved: return result return None def getLastApprovedRigidTransformation(self): if sum([1 for result in self.registrationResults.values() if result.approved]) == 1: lastRigidTfm = None else: lastRigidTfm = self.getMostRecentApprovedResult().transforms.rigid if not lastRigidTfm: lastRigidTfm = slicer.vtkMRMLLinearTransformNode() slicer.mrmlScene.AddNode(lastRigidTfm) return lastRigidTfm @onExceptionReturnNone def getMostRecentApprovedTransform(self): seriesTypeManager = SeriesTypeManager() results = sorted(self.registrationResults.values(), key=lambda s: s.seriesNumber) for result in reversed(results): if result.approved and not seriesTypeManager.isCoverProstate(result.name): return result.getTransform(result.registrationType) return None @onExceptionReturnNone def getResult(self, series): return self.registrationResults[series] def getResultsBySeries(self, series): seriesNumber = RegistrationResult.getSeriesNumberFromString(series) return self.getResultsBySeriesNumber(seriesNumber) def getResultsBySeriesNumber(self, seriesNumber): return [result for result in self.getResultsAsList() if seriesNumber == result.seriesNumber] def removeResult(self, series): # TODO: is this method ever used? try: del self.registrationResults[series] except KeyError: pass def exists(self, series): return series in self.registrationResults.keys() @onExceptionReturnNone def getMostRecentApprovedResult(self, priorToSeriesNumber=None): results = sorted(self.registrationResults.values(), key=lambda s: s.seriesNumber) if priorToSeriesNumber: results = [result for result in results if result.seriesNumber < priorToSeriesNumber] for result in reversed(results): if result.approved: return result return None def getApprovedOrLastResultForSeries(self, series): results = self.getResultsBySeries(series) for result in results: if result.approved: return result return results[-1]
class ProstateAblationSession(StepBasedSession): IncomingDataSkippedEvent = SlicerDevelopmentToolboxEvents.SkippedEvent IncomingIntraopDataReceiveFinishedEvent = SlicerDevelopmentToolboxEvents.FinishedEvent + 111 NewImageSeriesReceivedEvent = SlicerDevelopmentToolboxEvents.NewImageDataReceivedEvent DICOMReceiverStatusChanged = SlicerDevelopmentToolboxEvents.StatusChangedEvent DICOMReceiverStoppedEvent = SlicerDevelopmentToolboxEvents.StoppedEvent ZFrameRegistrationSuccessfulEvent = vtk.vtkCommand.UserEvent + 140 LoadingMetadataSuccessfulEvent = vtk.vtkCommand.UserEvent + 143 SegmentationCancelledEvent = vtk.vtkCommand.UserEvent + 144 CurrentSeriesChangedEvent = vtk.vtkCommand.UserEvent + 151 InitiateZFrameCalibrationEvent = vtk.vtkCommand.UserEvent + 160 InitiateTargetingEvent = vtk.vtkCommand.UserEvent + 161 NeedleTipLocateEvent = vtk.vtkCommand.UserEvent + 162 NeedleGuidanceEvent = vtk.vtkCommand.UserEvent + 164 AffectedAreaDisplayChangedEvent = vtk.vtkCommand.UserEvent + 165 SeriesTypeManuallyAssignedEvent = SeriesTypeManager.SeriesTypeManuallyAssignedEvent MODULE_NAME = constants.MODULE_NAME NEEDLE_NAME = 'NeedlePath' AFFECTEDAREA_NAME = "AffectedArea" ISSEEDTYPE = "IceSeed" ISRODTYPE = "IceRod" @property def intraopDICOMDirectory(self): return os.path.join(self.directory, "DICOM", "Intraop") if self.directory else None @property def outputDirectory(self): # was outputDir return os.path.join(self.directory, "ProstateAblationOutputs") @property def approvedCoverTemplate(self): try: return self.data.zFrameRegistrationResult.volume except AttributeError: return None @approvedCoverTemplate.setter def approvedCoverTemplate(self, volume): self.data.zFrameRegistrationResult.volume = volume self.zFrameRegistrationSuccessful = volume is not None @property def zFrameRegistrationSuccessful(self): self._zFrameRegistrationSuccessful = getattr( self, "_zFrameRegistrationSuccessful", None) return self.data.zFrameRegistrationResult is not None and self._zFrameRegistrationSuccessful @zFrameRegistrationSuccessful.setter def zFrameRegistrationSuccessful(self, value): self._zFrameRegistrationSuccessful = value if self._zFrameRegistrationSuccessful: self.save() self.invokeEvent(self.ZFrameRegistrationSuccessfulEvent) @property def currentSeries(self): self._currentSeries = getattr(self, "_currentSeries", None) return self._currentSeries @currentSeries.setter def currentSeries(self, series): if series == self.currentSeries: return print "set current Series on session" if series and series not in self.seriesList: raise UnknownSeriesError("Series %s is unknown" % series) self._currentSeries = series self.invokeEvent(self.CurrentSeriesChangedEvent, series) @property def currentSeriesVolume(self): if not self.currentSeries: return None else: return self.getOrCreateVolumeForSeries(self.currentSeries) def __init__(self): StepBasedSession.__init__(self) self.seriesTypeManager = SeriesTypeManager() self.seriesTypeManager.addEventObserver( self.seriesTypeManager.SeriesTypeManuallyAssignedEvent, lambda caller, event: self.invokeEvent( self.SeriesTypeManuallyAssignedEvent)) self.targetingPlugin = TargetsDefinitionPlugin(self) self.needlePathCaculator = ZFrameGuidanceComputation(self) self.segmentationEditor = slicer.qMRMLSegmentEditorWidget() self.resetAndInitializeMembers() self.resetAndInitializedTargetsAndSegments() def resetAndInitializeMembers(self): self.seriesTypeManager.clear() self.initializeColorNodes() self.directory = None self.data = SessionData() self.trainingMode = False self.resetIntraopDICOMReceiver() self.loadableList = {} self.seriesList = [] self.alreadyLoadedSeries = {} self._currentSeries = None self.retryMode = False self.lastSelectedModelIndex = None self.previousStep = None def resetAndInitializedTargetsAndSegments(self): self.displayForTargets = dict() self.needleTypeForTargets = dict() self.targetingPlugin.cleanup() self.needleModelNode = None self.affectedAreaModelNode = None self.segmentationEditorNoneButton = None self.segmentationEditorShow3DButton = None self.segmentationEditorMaskOverWriteCombox = None self.segmentEditorNode = None self.setupNeedleAndSegModelNode() def initializeColorNodes(self): self.segmentedColorName = self.getSetting("Segmentation_Color_Name") def __del__(self): super(ProstateAblationSession, self).__del__() self.clearData() def clearData(self): slicer.mrmlScene.Clear(0) self.resetAndInitializeMembers() self.resetAndInitializedTargetsAndSegments() self.resetSteps() def resetSteps(self): for step in self.steps: step.cleanup() def onMrmlSceneCleared(self, caller, event): self.initializeColorNodes() @onExceptionReturnFalse def isCurrentSeriesCoverProstate(self): return self.seriesTypeManager.isCoverProstate(self.currentSeries) def isCaseDirectoryValid(self): return os.path.exists(self.intraopDICOMDirectory) def isRunning(self): return not self.directory in [None, ''] def setupFiducialWidgetAndTableWidget(self): self.targetingPlugin.fiducialsWidget.addEventObserver( slicer.vtkMRMLMarkupsNode().MarkupAddedEvent, self.updateAffectiveZoneAndDistance) self.targetingPlugin.fiducialsWidget.addEventObserver( slicer.vtkMRMLMarkupsNode().MarkupRemovedEvent, self.updateAffectiveZoneAndDistance) self.targetingPlugin.targetTablePlugin.addEventObserver( self.targetingPlugin.targetTablePlugin.TargetPosUpdatedEvent, self.updateAffectiveZoneAndDistance) def processDirectory(self): self.newCaseCreated = getattr(self, "newCaseCreated", False) if self.newCaseCreated: return if not self.directory or not self.isCaseDirectoryValid(): slicer.util.warningDisplay( "The selected case directory seems not to be valid", windowTitle="ProstateAblation") self.close(save=False) else: self.loadCaseData() self.invokeEvent(self.CaseOpenedEvent) def createNewCase(self, destination): self.newCaseCreated = True self.clearData() self.directory = destination self.createDirectory(self.intraopDICOMDirectory) self.createDirectory(self.outputDirectory) self.startIntraopDICOMReceiver() self.invokeEvent(self.IncomingDataSkippedEvent) self.newCaseCreated = False self.invokeEvent(self.NewCaseStartedEvent) def close(self, save=False): if not self.isRunning(): return message = None if save: self.data.savedNeedleTypeForTargets = self.needleTypeForTargets.copy( ) success, failedFileNames = self.data.close(self.outputDirectory) message = "Case data has been saved successfully." if success else \ "The following data failed to saved:\n %s" % failedFileNames self.invokeEvent(self.CloseCaseEvent, str(message)) self.clearData() def save(self): self.data.savedNeedleTypeForTargets = self.needleTypeForTargets.copy() success, failedFileNames = self.data.save(self.outputDirectory) return success and not len( failedFileNames ), "The following data failed to saved:\n %s" % failedFileNames def complete(self): self.data.completed = True self.close(save=True) def load(self): filename = os.path.join(self.outputDirectory, constants.JSON_FILENAME) completed = self.data.wasSessionCompleted(filename) if slicer.util.confirmYesNoDisplay("A %s session has been found for the selected case. Do you want to %s?" \ % ("completed" if completed else "started", "open it" if completed else "continue this session")): slicer.app.layoutManager().blockSignals(True) self._loading = True self.data.load(filename) self.postProcessLoadedSessionData() self._loading = False slicer.app.layoutManager().blockSignals(False) self.invokeEvent(self.LoadingMetadataSuccessfulEvent) else: self.clearData() def postProcessLoadedSessionData(self): for step in self.steps: step.resetAndInitialize() if self.data.zFrameRegistrationResult: self.setupLoadedTransform() self.data.resumed = not self.data.completed if self.data.intraOpTargets: for fiducialIndex in range( self.data.intraOpTargets.GetNumberOfFiducials()): self.displayForTargets[self.data.intraOpTargets.GetNthMarkupID( fiducialIndex)] = qt.Qt.Unchecked self.needleTypeForTargets = self.data.savedNeedleTypeForTargets.copy( ) self.targetingPlugin.targetTablePlugin.currentTargets = self.data.intraOpTargets self.targetingPlugin.targetTablePlugin.visible = True self.targetingPlugin.calculateTargetsDistance() self.targetingPlugin.targetDistanceWidget.visible = True self.setupLoadedTargets() self.startIntraopDICOMReceiver() def setupSegmentationWidget(self): for child in self.segmentationEditor.children(): if child.className() == 'QGroupBox': if child.title == 'Effects': self.segmentationEditorNoneButton = child.children()[1] if child.className() == 'ctkMenuButton': if child.text == ' Show 3D': self.segmentationEditorShow3DButton = child if child.className() == 'ctkCollapsibleGroupBox': if child.title == 'Masking': for grandchild in child.children(): if grandchild.className() == 'QComboBox': if grandchild.findText('All segments') > -1 and \ grandchild.findText('Visible segments') > -1 and \ grandchild.findText('None') > -1: self.segmentationEditorMaskOverWriteCombox = grandchild def clearOldNodesByName(self, name): collection = slicer.mrmlScene.GetNodesByName(name) for index in range(collection.GetNumberOfItems()): slicer.mrmlScene.RemoveNode(collection.GetItemAsObject(index)) def setupNeedleAndSegModelNode(self): self.clearOldNodesByName(self.NEEDLE_NAME) self.setupFiducialWidgetAndTableWidget() self.setupSegmentationWidget() if self.needleModelNode is None: self.needleModelNode = ModuleLogicMixin.createModelNode( self.NEEDLE_NAME) if (self.needleModelNode.GetScene() is None) or ( not self.needleModelNode.GetScene() == slicer.mrmlScene): slicer.mrmlScene.AddNode(self.needleModelNode) if self.needleModelNode.GetDisplayNode() is None: ModuleLogicMixin.createAndObserveDisplayNode( self.needleModelNode, displayNodeClass=slicer.vtkMRMLModelDisplayNode) self.needleModelNode.GetDisplayNode().SetColor(1.0, 0.0, 0.0) if self.affectedAreaModelNode is None: self.affectedAreaModelNode = ModuleLogicMixin.createModelNode( self.AFFECTEDAREA_NAME) if (self.affectedAreaModelNode.GetScene() is None) or ( not self.affectedAreaModelNode.GetScene() == slicer.mrmlScene): slicer.mrmlScene.AddNode(self.affectedAreaModelNode) if self.affectedAreaModelNode.GetDisplayNode() is None: ModuleLogicMixin.createAndObserveDisplayNode( self.affectedAreaModelNode, displayNodeClass=slicer.vtkMRMLModelDisplayNode) self.affectedAreaModelNode.GetDisplayNode().SetOpacity(0.5) self.affectedAreaModelNode.GetDisplayNode().SetColor(0.0, 1.0, 0.0) if self.data.segmentModelNode is None: # Create segmentation self.data.segmentModelNode = slicer.vtkMRMLSegmentationNode() slicer.mrmlScene.AddNode(self.data.segmentModelNode) self.data.segmentModelNode.CreateDefaultDisplayNodes( ) # only needed for display self.data.segmentModelNode.CreateDefaultStorageNode() self.data.segmentModelNode.SetName("IntraOpSegmentation") if (self.data.segmentModelNode.GetScene() is None) or ( not self.data.segmentModelNode.GetScene() == slicer.mrmlScene): slicer.mrmlScene.AddNode(self.data.segmentModelNode) if self.data.segmentModelNode.GetDisplayNode() is None: ModuleLogicMixin.createAndObserveDisplayNode( self.data.segmentModelNode, displayNodeClass=slicer.vtkMRMLSegmentationDisplayNode) if self.segmentEditorNode is None: self.segmentEditorNode = slicer.vtkMRMLSegmentEditorNode() slicer.mrmlScene.AddNode(self.segmentEditorNode) if (self.segmentEditorNode.GetScene() is None) or ( not self.segmentEditorNode.GetScene() == slicer.mrmlScene): slicer.mrmlScene.AddNode(self.segmentEditorNode) self.segmentationEditor.setMRMLScene(slicer.mrmlScene) self.segmentationEditor.setMRMLSegmentEditorNode( self.segmentEditorNode) self.segmentationEditorMaskOverWriteCombox.setCurrentIndex( self.segmentationEditorMaskOverWriteCombox.findText('None')) def updateAffectiveZoneAndDistance(self, caller=None, event=None): self.updateAffectiveZone() self.targetingPlugin.calculateTargetsDistance() def onShowAffectiveZoneToggled(self, checked): targetingNode = self.targetingPlugin.targetTablePlugin.currentTargets if targetingNode is not None: for targetIndex in range(targetingNode.GetNumberOfFiducials()): checkboxStatus = qt.Qt.Checked if checked else qt.Qt.Unchecked self.displayForTargets[targetingNode.GetNthMarkupID( targetIndex)] = checkboxStatus if self.targetingPlugin.targetTablePlugin.checkBoxList.get( targetingNode.GetNthMarkupID(targetIndex)): self.targetingPlugin.targetTablePlugin.checkBoxList[ targetingNode.GetNthMarkupID(targetIndex)].setChecked( checkboxStatus) self.updateAffectiveZone() if not self.segmentationEditorShow3DButton.isChecked() == checked: self.segmentationEditorShow3DButton.checked = checked if self.data.segmentModelNode: if not self.data.segmentModelNode.GetDisplayNode( ).GetVisibility() == checked: self.data.segmentModelNode.GetDisplayNode().SetVisibility( checked) def updateAffectiveZone(self, caller=None, event=None): targetingNode = self.targetingPlugin.targetTablePlugin.currentTargets if self.targetingPlugin.fiducialsWidget.visible: targetingNode = self.targetingPlugin.fiducialsWidget.currentNode if self.needleModelNode and self.affectedAreaModelNode and self.approvedCoverTemplate and targetingNode.GetNumberOfFiducials( ): needleModelAppend = vtk.vtkAppendPolyData() affectedBallAreaAppend = vtk.vtkAppendPolyData() zFrameTransformMatrix = self.data.zFrameRegistrationResult.transform.GetMatrixTransformToParent( ) # The offset and ellipsoid parameters are taken from the following source code # http://viewvc.slicer.org/viewvc.cgi/NAMICSandBox/trunk/IGTLoadableModules/ProstateNav/TransPerinealProstateCryoTemplate/vtkMRMLTransPerinealProstateCryoTemplateNode.cxx?revision=8043&view=markup offsetFromTip = 5.0 #unit mm coneHeight = 5.0 for targetIndex in range(targetingNode.GetNumberOfFiducials()): if self.displayForTargets.get( targetingNode.GetNthMarkupID( targetIndex)) == qt.Qt.Checked: affectedBallAreaRadius = self.GetIceBallRadius( self.needleTypeForTargets.get( targetingNode.GetNthMarkupID( targetIndex))) # unit mm targetPosition = [0.0, 0.0, 0.0] targetingNode.GetNthFiducialPosition( targetIndex, targetPosition) (start, end, indexX, indexY, depth, inRange) = self.needlePathCaculator.computeNearestPath( targetPosition) needleDirection = (numpy.array(end) - numpy.array(start)) / numpy.linalg.norm( numpy.array(end) - numpy.array(start)) cone = vtk.vtkConeSource() cone.SetRadius(1.5) cone.SetResolution(6) cone.SetHeight(coneHeight) cone.CappingOff() cone.Update() transform = vtk.vtkTransform() transform.RotateY(-90) transform.RotateX(30) transform.Translate(-coneHeight / 2, 0.0, 0.0) tFilter0 = vtk.vtkTransformPolyDataFilter() tFilter0.SetInputData(cone.GetOutput()) tFilter0.SetTransform(transform) tFilter0.Update() translatePart = start + depth * needleDirection for index, posElement in enumerate(translatePart): zFrameTransformMatrix.SetElement(index, 3, posElement) transform.SetMatrix(zFrameTransformMatrix) tFilter1 = vtk.vtkTransformPolyDataFilter() tFilter1.SetTransform(transform) tFilter1.SetInputData(tFilter0.GetOutput()) tFilter1.Update() needleModelAppend.AddInputData(tFilter1.GetOutput()) needleModelAppend.Update() pathTubeFilter = ModuleLogicMixin.createVTKTubeFilter( start, start + (depth - coneHeight) * needleDirection, radius=1.5, numSides=6) needleModelAppend.AddInputData(pathTubeFilter.GetOutput()) needleModelAppend.Update() #End of needle model #-------------- #-------------- #Begin of affectedBallArea affectedBallArea = vtk.vtkParametricEllipsoid() affectedBallArea.SetXRadius( float(affectedBallAreaRadius[0])) affectedBallArea.SetYRadius( float(affectedBallAreaRadius[1])) affectedBallArea.SetZRadius( float(affectedBallAreaRadius[2])) affectedBallAreaSource = vtk.vtkParametricFunctionSource() affectedBallAreaSource.SetParametricFunction( affectedBallArea) affectedBallAreaSource.SetScalarModeToV() affectedBallAreaSource.Update() translatePart = start + (depth + offsetFromTip - float( affectedBallAreaRadius[2])) * needleDirection for index, posElement in enumerate(translatePart): zFrameTransformMatrix.SetElement(index, 3, posElement) transform.SetMatrix(zFrameTransformMatrix) tFilter2 = vtk.vtkTransformPolyDataFilter() tFilter2.SetTransform(transform) tFilter2.SetInputData(affectedBallAreaSource.GetOutput()) tFilter2.Update() affectedBallAreaAppend.AddInputData(tFilter2.GetOutput()) affectedBallAreaAppend.Update() self.needleModelNode.SetAndObservePolyData( needleModelAppend.GetOutput()) self.affectedAreaModelNode.SetAndObservePolyData( affectedBallAreaAppend.GetOutput()) ModuleLogicMixin.setNodeVisibility(self.needleModelNode, True) ModuleLogicMixin.setNodeVisibility(self.affectedAreaModelNode, True) ModuleLogicMixin.setNodeSliceIntersectionVisibility( self.needleModelNode, True) ModuleLogicMixin.setNodeSliceIntersectionVisibility( self.affectedAreaModelNode, True) pass def setupLoadedTransform(self): self._zFrameRegistrationSuccessful = True self.steps[1].applyZFrameTransform() def setupLoadedTargets(self): if self.data.intraOpTargets: targets = self.data.intraOpTargets ModuleWidgetMixin.setFiducialNodeVisibility(targets, show=True) self.applyDefaultTargetDisplayNode(targets) self.markupsLogic.JumpSlicesToNthPointInMarkup(targets.GetID(), 0) def startIntraopDICOMReceiver(self): logging.info("Starting DICOM Receiver for intra-procedural data") if not self.data.completed: self.resetIntraopDICOMReceiver() self.intraopDICOMReceiver = SmartDICOMReceiver( self.intraopDICOMDirectory) self._observeIntraopDICOMReceiverEvents() self.intraopDICOMReceiver.start(not ( self.trainingMode or self.data.completed)) else: self.invokeEvent(SlicerDevelopmentToolboxEvents.StoppedEvent) self.importDICOMSeries(self.getFileList(self.intraopDICOMDirectory)) if self.intraopDICOMReceiver: self.intraopDICOMReceiver.forceStatusChangeEventUpdate() def resetIntraopDICOMReceiver(self): self.intraopDICOMReceiver = getattr(self, "intraopDICOMReceiver", None) if self.intraopDICOMReceiver: self.intraopDICOMReceiver.stop() self.intraopDICOMReceiver.removeEventObservers() def _observeIntraopDICOMReceiverEvents(self): self.intraopDICOMReceiver.addEventObserver( self.intraopDICOMReceiver.IncomingDataReceiveFinishedEvent, self.onDICOMSeriesReceived) self.intraopDICOMReceiver.addEventObserver( SlicerDevelopmentToolboxEvents.StatusChangedEvent, self.onDICOMReceiverStatusChanged) @vtk.calldata_type(vtk.VTK_STRING) def onDICOMReceiverStatusChanged(self, caller, event, callData): customStatusProgressBar = CustomStatusProgressbar() customStatusProgressBar.text = callData if "Waiting" in callData: customStatusProgressBar.busy = True @vtk.calldata_type(vtk.VTK_STRING) def onDICOMSeriesReceived(self, caller, event, callData): self.importDICOMSeries(ast.literal_eval(callData)) if self.trainingMode is True: self.resetIntraopDICOMReceiver() def importDICOMSeries(self, newFileList): indexer = ctk.ctkDICOMIndexer() newSeries = [] for currentIndex, currentFile in enumerate(newFileList, start=1): self.invokeEvent( SlicerDevelopmentToolboxEvents.NewFileIndexedEvent, [ "Indexing file %s" % currentFile, len(newFileList), currentIndex ].__str__()) slicer.app.processEvents() currentFile = os.path.join(self.intraopDICOMDirectory, currentFile) indexer.addFile(slicer.dicomDatabase, currentFile, None) series = self.makeSeriesNumberDescription(currentFile) if series not in self.seriesList: if not series.split(": ")[0] == '__TAG_NOT_IN_INSTANCE__': self.seriesList.append(series) newSeries.append(series) self.loadableList[ series] = self.createLoadableFileListForSeries(series) self.seriesList = sorted(self.seriesList, key=lambda s: int(s.split(": ")[0])) if len(newFileList): self.verifyPatientIDEquality(newFileList) self.invokeEvent(self.NewImageSeriesReceivedEvent, newSeries.__str__()) def verifyPatientIDEquality(self, receivedFiles): seriesNumberPatientID = self.getAdditionalInformationForReceivedSeries( receivedFiles) dicomFileName = self.getPatientIDValidationSource() if not dicomFileName: return currentInfo = self.getPatientInformation(dicomFileName) currentID = currentInfo["PatientID"] patientName = currentInfo["PatientName"] for seriesNumber, receivedInfo in seriesNumberPatientID.iteritems(): patientID = receivedInfo["PatientID"] if patientID is not None and patientID != currentID: m = 'WARNING:\n' \ 'Current case:\n' \ ' Patient ID: {0}\n' \ ' Patient Name: {1}\n' \ 'Received image\n' \ ' Patient ID: {2}\n' \ ' Patient Name : {3}\n\n' \ 'Do you want to keep this series? '.format(currentID, patientName, patientID, receivedInfo["PatientName"]) if not slicer.util.confirmYesNoDisplay( m, title="Patient IDs Not Matching", windowTitle="ProstateAblation"): self.deleteSeriesFromSeriesList(seriesNumber) def getPatientIDValidationSource(self): # TODO: For loading case purposes it would be nice to keep track which series were accepted if len(self.loadableList.keys()) > 1: keylist = self.loadableList.keys() keylist.sort(key=lambda x: int(x.split(": ")[0])) return self.loadableList[keylist[0]][0] else: return None def getOrCreateVolumeForSeries(self, series): try: volume = self.alreadyLoadedSeries[series] except KeyError: logging.info("Need to load volume") files = self.loadableList[series] loadables = self.scalarVolumePlugin.examine([files]) success, volume = slicer.util.loadVolume(files[0], returnNode=True) volume.SetName(loadables[0].name) self.alreadyLoadedSeries[series] = volume slicer.app.processEvents() return volume def createLoadableFileListForSeries(self, series): seriesNumber = int(series.split(": ")[0]) loadableList = [] for dcm in self.getFileList(self.intraopDICOMDirectory): currentFile = os.path.join(self.intraopDICOMDirectory, dcm) if self.getDICOMValue( currentFile, DICOMTAGS.SERIES_NUMBER) and (not self.getDICOMValue( currentFile, DICOMTAGS.SERIES_NUMBER) == '__TAG_NOT_IN_INSTANCE__'): try: int( self.getDICOMValue(currentFile, DICOMTAGS.SERIES_NUMBER)) except: print "nothing: ", self.getDICOMValue( currentFile, DICOMTAGS.SERIES_NUMBER) currentSeriesNumber = int( self.getDICOMValue(currentFile, DICOMTAGS.SERIES_NUMBER)) if currentSeriesNumber and currentSeriesNumber == seriesNumber: loadableList.append(currentFile) return loadableList def deleteSeriesFromSeriesList(self, seriesNumber): for series in self.seriesList: currentSeriesNumber = int(series.split(": ")[0]) if currentSeriesNumber == seriesNumber: self.seriesList.remove(series) for seriesFile in self.loadableList[series]: logging.debug( "removing {} from filesystem".format(seriesFile)) os.remove(seriesFile) del self.loadableList[series] def makeSeriesNumberDescription(self, dcmFile): seriesDescription = self.getDICOMValue(dcmFile, DICOMTAGS.SERIES_DESCRIPTION) seriesNumber = self.getDICOMValue(dcmFile, DICOMTAGS.SERIES_NUMBER) if not (seriesNumber and seriesDescription): raise DICOMValueError( "Missing Attribute(s):\nFile: {}\nseriesNumber: {}\nseriesDescription: {}" .format(dcmFile, seriesNumber, seriesDescription)) return "{}: {}".format(seriesNumber, seriesDescription) def getAdditionalInformationForReceivedSeries(self, fileList): seriesNumberPatientID = {} for currentFile in [ os.path.join(self.intraopDICOMDirectory, f) for f in fileList ]: seriesNumber = int( self.getDICOMValue(currentFile, DICOMTAGS.SERIES_NUMBER)) if seriesNumber not in seriesNumberPatientID.keys(): seriesNumberPatientID[ seriesNumber] = self.getPatientInformation(currentFile) return seriesNumberPatientID def getPatientInformation(self, currentFile): return { "PatientID": self.getDICOMValue(currentFile, DICOMTAGS.PATIENT_ID), "PatientName": self.getDICOMValue(currentFile, DICOMTAGS.PATIENT_NAME), "SeriesDescription": self.getDICOMValue(currentFile, DICOMTAGS.SERIES_DESCRIPTION) } def getSeriesForSubstring(self, substring): for series in reversed(self.seriesList): if substring in series: return series return None def loadCaseData(self): if not os.path.exists( os.path.join(self.outputDirectory, constants.JSON_FILENAME)): if len(os.listdir(self.intraopDICOMDirectory)): self.startIntraopDICOMReceiver() else: self.openSavedSession() def openSavedSession(self): self.load() def applyDefaultTargetDisplayNode(self, targetNode, new=False): displayNode = None if new else targetNode.GetDisplayNode() modifiedDisplayNode = self.setupDisplayNode(displayNode, True) targetNode.SetAndObserveDisplayNodeID(modifiedDisplayNode.GetID()) def setupDisplayNode(self, displayNode=None, starBurst=False): if not displayNode: displayNode = slicer.vtkMRMLMarkupsDisplayNode() slicer.mrmlScene.AddNode(displayNode) displayNode.SetTextScale(0) displayNode.SetGlyphScale(2.5) if starBurst: displayNode.SetGlyphType( slicer.vtkMRMLAnnotationPointDisplayNode.StarBurst2D) return displayNode def loadProcessedData(self, directory): resourcesDir = os.path.join(directory, 'RESOURCES') logging.debug(resourcesDir) if not os.path.exists(resourcesDir): message = "The selected directory does not fit the directory structure. Make sure that you select the " \ "study root directory which includes directories RESOURCES" return message seriesMap = [] self.loadImageAndLabel(seriesMap) if self.segmentationPath is None: message = "No segmentations found.\nMake sure that you used segment editor for segmenting the prostate first and using " \ "its output as the data input here." return message return None def loadImageAndLabel(self, seriesMap): self.imagePath = None self.segmentationPath = None segmentedColorName = self.getSetting("Segmentation_Color_Name") for series in seriesMap: seriesName = str(seriesMap[series]['LongName']) logging.debug('series Number ' + series + ' ' + seriesName) imagePath = os.path.join(seriesMap[series]['NRRDLocation']) segmentationPath = os.path.dirname(os.path.dirname(imagePath)) segmentationPath = os.path.join(segmentationPath, 'Segmentations') if not os.path.exists(segmentationPath): continue else: if any(segmentedColorName in name for name in os.listdir(segmentationPath)): logging.debug(' FOUND THE SERIES OF INTEREST, ITS ' + seriesName) logging.debug(' LOCATION OF VOLUME : ' + str(seriesMap[series]['NRRDLocation'])) logging.debug(' LOCATION OF IMAGE path : ' + str(imagePath)) logging.debug(' LOCATION OF SEGMENTATION path : ' + segmentationPath) self.imagePath = seriesMap[series]['NRRDLocation'] self.segmentationPath = segmentationPath break def isTrackingPossible(self, series): if self.data.completed: logging.debug( "No tracking possible. Case has been marked as completed!") return False else: return True def isEligibleForDistanceMeasure(self, series): seriesType = self.seriesTypeManager.getSeriesType(series) listItems = [ str(item) for item in self.getSetting("COVER_PROSTATE") + self.getSetting("COVER_TEMPLATE") + self.getSetting("VIBE_IMAGE") ] return self.isAnyListItemInString(seriesType, listItems) def isLoading(self): self._loading = getattr(self, "_loading", False) return self._loading def GetIceBallRadius(self, type): if type == self.ISRODTYPE: return numpy.array(self.getSetting("NeedleRadius_ICEROD").split()) elif type == self.ISSEEDTYPE: return numpy.array(self.getSetting("NeedleRadius_ICESEED").split()) else: needleRadius = numpy.array([0, 0, 0]) return needleRadius def takeActionForCurrentSeries(self, event=None): callData = None if self.seriesTypeManager.isCoverTemplate(self.currentSeries): event = self.InitiateZFrameCalibrationEvent else: if self.zFrameRegistrationSuccessful: if self.seriesTypeManager.isCoverProstate(self.currentSeries): event = self.InitiateTargetingEvent elif self.seriesTypeManager.isGuidance(self.currentSeries): event = self.NeedleGuidanceEvent else: event = self.NeedleGuidanceEvent else: slicer.util.warningDisplay( "ZFrame registration was not performed yet, it is required!" ) if event: self.invokeEvent(event, callData) else: raise UnknownSeriesError( "Action for currently selected series unknown") @onReturnProcessEvents def updateProgressBar(self, **kwargs): if self.progress: for key, value in kwargs.iteritems(): if hasattr(self.progress, key): setattr(self.progress, key, value)