Ejemplo n.º 1
0
    def runAutomaticSegmentation(self):
        logic = AutomaticSegmentationLogic()
        logic.addEventObserver(logic.DeepLearningFinishedEvent,
                               self.onSegmentationFinished)
        logic.addEventObserver(
            logic.DeepLearningFailedEvent, lambda c, e: self.
            onPreopLoadingFailed("Automatic segmentation failed."))
        customStatusProgressBar = CustomStatusProgressbar()
        customStatusProgressBar.text = "Running DeepInfer for automatic prostate segmentation"

        from mpReview import mpReviewLogic
        mpReviewColorNode, _ = mpReviewLogic.loadColorTable(
            self.getSetting("Color_File_Name", moduleName=self.MODULE_NAME))

        domain = 'BWH_WITHOUT_ERC'
        prompt = SliceWidgetConfirmYesNoDialog(
            self.data.initialVolume,
            "Was an endorectal coil used during preop acquisition?").exec_()

        self.preopData.usedERC = False
        if prompt == qt.QDialogButtonBox.Yes:
            self.preopData.usedERC = True
            domain = 'BWH_WITH_ERC'
        elif prompt == qt.QDialogButtonBox.Cancel:
            raise PreProcessedDataError

        self.segmentationData.startTime = self.getTime()
        logic.run(self.data.initialVolume, domain, mpReviewColorNode)
Ejemplo n.º 2
0
    def onNewImageSeriesReceived(self, caller, event, callData):
        if not self.session.isBusy():
            customStatusProgressBar = CustomStatusProgressbar()
            customStatusProgressBar.text = "New image data has been received."
            if self.session.intraopDICOMReceiver:
                self.session.intraopDICOMReceiver.forceStatusChangeEventUpdate(
                )

        self.updateIntraopSeriesSelectorTable()

        if not self.active or self.session.isBusy():
            return

        selectedSeries = self.intraopSeriesSelector.currentText
        if selectedSeries != "" and self.session.isTrackingPossible(
                selectedSeries):
            selectedSeriesNumber = RegistrationResult.getSeriesNumberFromString(
                selectedSeries)

            newImageSeries = ast.literal_eval(callData)
            newImageSeriesNumbers = [
                RegistrationResult.getSeriesNumberFromString(s)
                for s in newImageSeries
            ]
            if selectedSeriesNumber in newImageSeriesNumbers:
                self.takeActionOnSelectedSeries()
Ejemplo n.º 3
0
  def promptUserAndApplyBiasCorrectionIfNeeded(self):
    if self.session.data.resumed or self.session.data.completed:
      return

    autoPreopSeg = self.session.data.usedAutomaticPreopSegmentation
    usedERC = (autoPreopSeg and self.session.data.preopData.usedERC is True) or \
              (not autoPreopSeg and slicer.util.confirmYesNoDisplay("Was an endorectal coil used for preop image "
                                                                    "acquisition?", windowTitle="SliceTracker"))
    if usedERC:
      customProgressbar = CustomStatusProgressbar()
      customProgressbar.busy = True
      currentModule = slicer.util.getModuleGui(self.MODULE_NAME)
      if currentModule:
        currentModule.parent().enabled = False
      try:
        customProgressbar.updateStatus("Bias correction running!")
        slicer.app.processEvents()
        self.logic.applyBiasCorrection()
      except AttributeError:
        pass
      finally:
        customProgressbar.busy = False
        if currentModule:
          currentModule.parent().enabled = True
      customProgressbar.updateStatus("Bias correction done!")

    self.session.data.preopData.usedERC = usedERC
Ejemplo n.º 4
0
  def setup(self):
    ScriptedLoadableModuleWidget.setup(self)

    for step in [SliceTrackerOverviewStep, SliceTrackerZFrameRegistrationStep,
                 SliceTrackerSegmentationStep, SliceTrackerEvaluationStep]:
      self.session.registerStep(step())

    self.customStatusProgressBar = CustomStatusProgressbar()
    self.setupPatientWatchBox()
    self.setupViewSettingGroupBox()
    self.setupTabBarNavigation()
    self.setupConnections()
    self.setupSessionObservers()
    self.layout.addStretch(1)
Ejemplo n.º 5
0
 def onSegmentationValidated(self, caller, event, labelNode):
   if "_modified" in labelNode.GetName():
     self.segmentationData.userModified["endTime"] = self.getTime()
     self.segmentationData._modifiedLabel = labelNode
   else:
     self.segmentationData.userModified = None
   if not self.preopSegmentationPath:
     self.createDirectory(self.preopSegmentationPath)
   segmentedColorName = self.getSetting("Segmentation_Color_Name", moduleName=self.MODULE_NAME)
   timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
   filename = getpass.getuser() + '-' + segmentedColorName + '-' + timestamp
   self.saveNodeData(labelNode, self.preopSegmentationPath, extension=FileExtension.NRRD, name=filename)
   self.loadPreProcessedData()
   customStatusProgressBar = CustomStatusProgressbar()
   customStatusProgressBar.text = "Done automatic prostate segmentation"
    def setup(self):
        ScriptedLoadableModuleWidget.setup(self)

        for step in [
                ProstateAblationOverviewStep,
                ProstateAblationZFrameRegistrationStep,
                ProstateAblationTargetingStep, ProstateAblationGuidanceStep
        ]:
            registeredStep = step(self.session)
            self.session.registerStep(registeredStep)

        self.customStatusProgressBar = CustomStatusProgressbar()
        self.setupPatientWatchBox()
        self.setupViewSettingGroupBox()
        self.setupTabBarNavigation()
        self.setupSessionObservers()
Ejemplo n.º 7
0
  def onNewImageSeriesReceived(self, caller, event, callData):
    if not self.session.isLoading():
      customStatusProgressBar = CustomStatusProgressbar()
      customStatusProgressBar.text = "New image data has been received."

    self.updateIntraopSeriesSelectorTable()

    if not self.active or self.session.isLoading():
      return
    selectedSeries = self.intraopSeriesSelector.currentText
    if selectedSeries != "" and self.session.isTrackingPossible(selectedSeries):
      selectedSeriesNumber = int(selectedSeries.split(": ")[0])

      newImageSeries = ast.literal_eval(callData)
      newImageSeriesNumbers = [int(s.split(": ")[0]) for s in newImageSeries]
      if selectedSeriesNumber in newImageSeriesNumbers:
        self.takeActionOnSelectedSeries()
 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()
Ejemplo n.º 9
0
    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 = {}
Ejemplo n.º 10
0
 def onDICOMReceiverStatusChanged(self, caller, event, callData):
     customStatusProgressBar = CustomStatusProgressbar()
     customStatusProgressBar.text = callData
     customStatusProgressBar.busy = "Waiting" in callData
Ejemplo n.º 11
0
class SliceTrackerWidget(ModuleWidgetMixin, SliceTrackerConstants, ScriptedLoadableModuleWidget):

  def __init__(self, parent=None):
    ScriptedLoadableModuleWidget.__init__(self, parent)
    self.modulePath = os.path.dirname(slicer.util.modulePath(self.moduleName))
    SliceTrackerConfiguration(self.moduleName, os.path.join(self.modulePath, 'Resources', "default.cfg"))
    self.logic = SliceTrackerLogic()

    self.session = SliceTrackerSession()
    self.session.steps = []
    self.session.removeEventObservers()
    self.session.addEventObserver(self.session.CloseCaseEvent, lambda caller, event: self.cleanup())
    self.session.addEventObserver(SlicerDevelopmentToolboxEvents.NewFileIndexedEvent, self.onNewFileIndexed)
    self.demoMode = str(self.getSetting("Demo_Mode", moduleName=self.moduleName)).lower() == 'true'

  def enter(self):
    if not slicer.dicomDatabase:
      slicer.util.errorDisplay("Slicer DICOMDatabase was not found. In order to be able to use SliceTracker, you will "
                               "need to set a proper location for the Slicer DICOMDatabase.")
    self.layout.parent().enabled = slicer.dicomDatabase is not None

  def exit(self):
    pass

  def onReload(self):
    ScriptedLoadableModuleWidget.onReload(self)

  @logmethod(logging.DEBUG)
  def cleanup(self):
    ScriptedLoadableModuleWidget.cleanup(self)
    self.patientWatchBox.sourceFile = None
    self.preopWatchBox.sourceFile = None
    self.intraopWatchBox.sourceFile = None

  def setup(self):
    ScriptedLoadableModuleWidget.setup(self)

    for step in [SliceTrackerOverviewStep, SliceTrackerZFrameRegistrationStep,
                 SliceTrackerSegmentationStep, SliceTrackerEvaluationStep]:
      self.session.registerStep(step())

    self.customStatusProgressBar = CustomStatusProgressbar()
    self.setupPatientWatchBox()
    self.setupViewSettingGroupBox()
    self.setupTabBarNavigation()
    self.setupConnections()
    self.setupSessionObservers()
    self.layout.addStretch(1)

  def setupPatientWatchBox(self):
    WatchBoxAttribute.TRUNCATE_LENGTH = 20
    patientWatchBoxInformation = [WatchBoxAttribute('PatientName', "Patient's Name: ", DICOMTAGS.PATIENT_NAME,
                                                    masked=self.demoMode),
                                  WatchBoxAttribute('PatientID', 'Patient ID: ', DICOMTAGS.PATIENT_ID,
                                                    masked=self.demoMode),
                                  WatchBoxAttribute('DOB', "Patient's Birth Date: ", DICOMTAGS.PATIENT_BIRTH_DATE,
                                                    masked=self.demoMode)]
    self.patientWatchBox = DICOMBasedInformationWatchBox(patientWatchBoxInformation, title="Patient Information",
                                                         columns=2)
    self.layout.addWidget(self.patientWatchBox)

    preopWatchBoxInformation = [WatchBoxAttribute('StudyDate', 'Study Date: ', DICOMTAGS.STUDY_DATE)]
    self.preopWatchBox = DICOMBasedInformationWatchBox(preopWatchBoxInformation, title="Preop Information", columns=2)
    self.layout.addWidget(self.preopWatchBox)

    intraopWatchBoxInformation = [WatchBoxAttribute('CurrentSeries', 'Current Series: ', [DICOMTAGS.SERIES_NUMBER,
                                                                                          DICOMTAGS.SERIES_DESCRIPTION]),
                                  WatchBoxAttribute('StudyDate', 'Study Date: ', DICOMTAGS.STUDY_DATE)]
    self.intraopWatchBox = DICOMBasedInformationWatchBox(intraopWatchBoxInformation, title="Intraop Information",
                                                         columns=2)
    self.registrationDetailsButton = self.createButton("", icon=Icons.settings, styleSheet="border:none;",
                                                       maximumWidth=16)
    self.layout.addWidget(self.intraopWatchBox)

  def setupViewSettingGroupBox(self):
    iconSize = qt.QSize(24, 24)
    self.infoButton = self.createButton("", icon=Icons.info, iconSize=iconSize, checkable=True,
                                        toolTip="Display Patient/Study Information", checked=True)
    self.redOnlyLayoutButton = RedSliceLayoutButton()
    self.sideBySideLayoutButton = SideBySideLayoutButton()
    self.fourUpLayoutButton = FourUpLayoutButton()
    self.fourUpPlotLayoutButton = FourUpPlotViewLayoutButton()
    self.layoutButtons = [self.redOnlyLayoutButton, self.sideBySideLayoutButton, self.fourUpLayoutButton, self.fourUpPlotLayoutButton]
    self.crosshairButton = CrosshairButton()
    self.settingsButton = ModuleSettingsButton(self.moduleName)
    self.dicomConnectionTestButton = DICOMConnectionTestButton()
    self.dicomConnectionTestButton.setToolTip("Test DICOM connection")
    self.showAnnotationsButton = self.createButton("", icon=Icons.text_info, iconSize=iconSize, checkable=True,
                                                   toolTip="Display annotations", checked=True)

    viewSettingButtons = [self.redOnlyLayoutButton, self.sideBySideLayoutButton, self.fourUpPlotLayoutButton,
                          self.fourUpLayoutButton, self.infoButton, self.crosshairButton,
                          self.settingsButton, self.dicomConnectionTestButton]

    for step in self.session.steps:
      viewSettingButtons += step.viewSettingButtons

    self.layout.addWidget(self.createHLayout(viewSettingButtons))

    self.resetViewSettingButtons()

  def resetViewSettingButtons(self):
    for step in self.session.steps:
      step.resetViewSettingButtons()
    self.crosshairButton.checked = False

  def setupTabBarNavigation(self):
    self.tabWidget = SliceTrackerTabWidget()
    self.tabWidget.addEventObserver(self.tabWidget.AvailableLayoutsChangedEvent, self.onAvailableLayoutsChanged)
    self.layout.addWidget(self.tabWidget)
    self.tabWidget.hideTabs()

  def setupConnections(self):
    self.infoButton.connect('toggled(bool)', self.onShowInformationToggled)
    self.showAnnotationsButton.connect('toggled(bool)', self.onShowAnnotationsToggled)

  def setupSessionObservers(self):
    self.session.addEventObserver(self.session.PreprocessingSuccessfulEvent, self.onSuccessfulPreProcessing)
    self.session.addEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
    self.session.addEventObserver(self.session.CloseCaseEvent, self.onCaseClosed)

  def removeSessionObservers(self):
    self.session.removeEventObserver(self.session.PreprocessingSuccessfulEvent, self.onSuccessfulPreProcessing)
    self.session.removeEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
    self.session.removeEventObserver(self.session.CloseCaseEvent, self.onCaseClosed)

  def onSuccessfulPreProcessing(self, caller, event):
    dicomFileName = self.logic.getFileList(self.session.preopDICOMDirectory)[0]
    filename = os.path.join(self.session.preopDICOMDirectory, dicomFileName)
    self.patientWatchBox.sourceFile = filename
    self.preopWatchBox.sourceFile = filename

  def onShowAnnotationsToggled(self, checked):
    allSliceAnnotations = self.sliceAnnotations[:]

  def onShowInformationToggled(self, checked):
    self.patientWatchBox.visible = checked
    self.preopWatchBox.visible = checked
    self.intraopWatchBox.visible = checked

  @vtk.calldata_type(vtk.VTK_STRING)
  def onNewFileIndexed(self, caller, event, callData):
    text, size, currentIndex = ast.literal_eval(callData)
    if not self.customStatusProgressBar.visible:
      self.customStatusProgressBar.show()
    self.customStatusProgressBar.maximum = size
    self.customStatusProgressBar.updateStatus(text, currentIndex)

  @vtk.calldata_type(vtk.VTK_STRING)
  def onCurrentSeriesChanged(self, caller, event, callData=None):
    receivedFile = self.session.loadableList[callData][0] if callData else None
    if not self.session.data.usePreopData and self.patientWatchBox.sourceFile is None:
      self.patientWatchBox.sourceFile = receivedFile
      self.preopWatchBox.setInformation("StudyDate", "NA")
    self.intraopWatchBox.sourceFile = receivedFile

  @vtk.calldata_type(vtk.VTK_STRING)
  def onAvailableLayoutsChanged(self, caller, event, callData):
    layouts = ast.literal_eval(callData)
    for layoutButton in self.layoutButtons:
      layoutButton.enabled = layoutButton.LAYOUT in layouts

  @vtk.calldata_type(vtk.VTK_STRING)
  def onCaseClosed(self, caller, event, callData):
    self.customStatusProgressBar.reset()
Ejemplo n.º 12
0
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.items():
                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.items():
            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 ProstateAblationWidget(ModuleWidgetMixin, ScriptedLoadableModuleWidget):
    def __init__(self, parent=None):
        ScriptedLoadableModuleWidget.__init__(self, parent)
        self.modulePath = os.path.dirname(
            slicer.util.modulePath(self.moduleName))
        ConfigurationParser(
            os.path.join(self.modulePath, 'Resources', "default.cfg"))
        self.logic = ProstateAblationLogic()

        self.session = ProstateAblationSession()
        self.session.steps = []
        self.session.removeEventObservers()
        self.session.addEventObserver(self.session.CloseCaseEvent,
                                      lambda caller, event: self.cleanup())
        self.session.addEventObserver(
            SlicerDevelopmentToolboxEvents.NewFileIndexedEvent,
            self.onNewFileIndexed)
        self.demoMode = False
        self.developerMode = True

    def enter(self):
        if not slicer.dicomDatabase:
            slicer.util.errorDisplay(
                "Slicer DICOMDatabase was not found. In order to be able to use ProstateAblation, you will "
                "need to set a proper location for the Slicer DICOMDatabase.")
        self.layout.parent().enabled = slicer.dicomDatabase is not None

    def exit(self):
        pass

    def onReload(self):
        ScriptedLoadableModuleWidget.onReload(self)

    @logmethod(logging.DEBUG)
    def cleanup(self):
        ScriptedLoadableModuleWidget.cleanup(self)
        #self.patientWatchBox.sourceFile = None
        #self.intraopWatchBox.sourceFile = None

    def setup(self):
        ScriptedLoadableModuleWidget.setup(self)

        for step in [
                ProstateAblationOverviewStep,
                ProstateAblationZFrameRegistrationStep,
                ProstateAblationTargetingStep, ProstateAblationGuidanceStep
        ]:
            registeredStep = step(self.session)
            self.session.registerStep(registeredStep)

        self.customStatusProgressBar = CustomStatusProgressbar()
        self.setupPatientWatchBox()
        self.setupViewSettingGroupBox()
        self.setupTabBarNavigation()
        self.setupSessionObservers()

    def setupPatientWatchBox(self):
        WatchBoxAttribute.TRUNCATE_LENGTH = 20
        self.patientWatchBoxInformation = [
            WatchBoxAttribute('PatientName',
                              'Name: ',
                              DICOMTAGS.PATIENT_NAME,
                              masked=self.demoMode),
            WatchBoxAttribute('PatientID',
                              'PID: ',
                              DICOMTAGS.PATIENT_ID,
                              masked=self.demoMode),
            WatchBoxAttribute('DOB',
                              'DOB: ',
                              DICOMTAGS.PATIENT_BIRTH_DATE,
                              masked=self.demoMode),
            WatchBoxAttribute('StudyDate', 'StudyDate: ', DICOMTAGS.STUDY_DATE)
        ]
        self.patientWatchBox = DICOMBasedInformationWatchBox(
            self.patientWatchBoxInformation,
            title="Patient Information",
            columns=2)
        self.layout.addWidget(self.patientWatchBox)

        intraopWatchBoxInformation = [
            WatchBoxAttribute(
                'CurrentSeries', 'Current Series: ',
                [DICOMTAGS.SERIES_NUMBER, DICOMTAGS.SERIES_DESCRIPTION]),
            WatchBoxAttribute('StudyDate', 'Intraop Date: ',
                              DICOMTAGS.STUDY_DATE)
        ]
        self.intraopWatchBox = DICOMBasedInformationWatchBox(
            intraopWatchBoxInformation, columns=2)
        self.layout.addWidget(self.intraopWatchBox)

    def setupViewSettingGroupBox(self):
        iconSize = qt.QSize(24, 24)
        self.redOnlyLayoutButton = RedSliceLayoutButton()
        self.sideBySideLayoutButton = SideBySideLayoutButton()
        self.fourUpLayoutButton = FourUpLayoutButton()
        self.layoutButtons = [
            self.redOnlyLayoutButton, self.sideBySideLayoutButton,
            self.fourUpLayoutButton
        ]

        self.screenShotButton = ScreenShotButton()
        self.screenShotButton.caseResultDir = ""
        self.settingsButton = ModuleSettingsButton(self.moduleName)
        self.affectiveZoneIcon = self.createIcon('icon-needle.png')
        self.showAffectiveZoneButton = self.createButton(
            "",
            icon=self.affectiveZoneIcon,
            iconSize=iconSize,
            checkable=True,
            toolTip="Display the effective ablation zone")
        self.showAffectiveZoneButton.connect(
            'toggled(bool)', self.session.onShowAffectiveZoneToggled)
        viewSettingButtons = [
            self.redOnlyLayoutButton, self.fourUpLayoutButton,
            self.settingsButton, self.screenShotButton,
            self.showAffectiveZoneButton
        ]

        for step in self.session.steps:
            viewSettingButtons += step.viewSettingButtons

        self.layout.addWidget(self.createHLayout(viewSettingButtons))
        self.setupAdditionalViewSettingButtons()
        self.resetViewSettingButtons()

    def setupAdditionalViewSettingButtons(self):
        for step in self.session.steps:
            step.setupIcons()
            step.setupAdditionalViewSettingButtons()

    def resetViewSettingButtons(self):
        for step in self.session.steps:
            step.resetViewSettingButtons()
        #self.crosshairButton.checked = False

    def setupTabBarNavigation(self):
        self.tabWidget = ProstateAblationTabWidget(self.session)
        self.tabWidget.addEventObserver(
            self.tabWidget.AvailableLayoutsChangedEvent,
            self.onAvailableLayoutsChanged)
        self.layout.addWidget(self.tabWidget)
        self.tabWidget.hideTabs()

    def setupSessionObservers(self):
        self.session.addEventObserver(self.session.CurrentSeriesChangedEvent,
                                      self.onCurrentSeriesChanged)
        self.session.addEventObserver(self.session.NewCaseStartedEvent,
                                      self.onUpdateScreenShotDir)
        self.session.addEventObserver(self.session.CaseOpenedEvent,
                                      self.onUpdateScreenShotDir)

    def removeSessionObservers(self):
        self.session.removeEventObserver(
            self.session.CurrentSeriesChangedEvent,
            self.onCurrentSeriesChanged)
        self.session.removeEventObserver(self.session.NewCaseStartedEvent,
                                         self.onUpdateScreenShotDir)
        self.session.removeEventObserver(self.session.CaseOpenedEvent,
                                         self.onUpdateScreenShotDir)

    def onUpdateScreenShotDir(self, caller, event):
        self.screenShotButton.caseResultDir = self.session.outputDirectory

    @vtk.calldata_type(vtk.VTK_STRING)
    def onNewFileIndexed(self, caller, event, callData):
        text, size, currentIndex = ast.literal_eval(callData)
        if not self.customStatusProgressBar.visible:
            self.customStatusProgressBar.show()
        self.customStatusProgressBar.maximum = size
        self.customStatusProgressBar.updateStatus(text, currentIndex)

    @vtk.calldata_type(vtk.VTK_STRING)
    def onCurrentSeriesChanged(self, caller, event, callData):
        receivedFile = self.session.loadableList[callData][
            0] if callData else None
        if self.patientWatchBox.sourceFile is None:
            self.patientWatchBox.sourceFile = receivedFile
        self.intraopWatchBox.sourceFile = receivedFile

    @vtk.calldata_type(vtk.VTK_STRING)
    def onAvailableLayoutsChanged(self, caller, event, callData):
        layouts = ast.literal_eval(callData)
        for layoutButton in self.layoutButtons:
            layoutButton.enabled = layoutButton.LAYOUT in layouts