class qSlicerMultiVolumeExplorerSimplifiedModuleWidget:
    def __init__(self, parent=None):
        logging.debug(
            "qSlicerMultiVolumeExplorerSimplifiedModuleWidget:init() called")
        if not parent or not hasattr(parent, "layout"):
            self.parent = slicer.qMRMLWidget()
            self.parent.setLayout(QVBoxLayout())
        else:
            self.parent = parent

        self.layout = self.parent.layout()

        self._bgMultiVolumeNode = None
        self._fgMultiVolumeNode = None

        self.styleObserverTags = []
        self.sliceWidgetsPerStyle = {}

        self.chartPopupWindow = None
        self.chartPopupSize = QSize(600, 300)
        self.chartPopupPosition = QPoint(0, 0)

    def hide(self):
        self.widget.hide()

    def show(self):
        self.widget.show()

    def setup(self):
        self.widget = QWidget()
        layout = QGridLayout()
        self.widget.setLayout(layout)
        self.layout.addWidget(self.widget)
        self.widget.show()
        self.layout = layout

        self.setupInputFrame()
        self.setupFrameControlFrame()
        self.setupAdditionalFrames()
        self.setupPlottingFrame()

        self.setFramesEnabled(False)

        self.timer = QTimer()
        self.timer.setInterval(50)

        self.setupConnections()

        # initialize slice observers (from DataProbe.py)
        # keep list of pairs: [observee,tag] so they can be removed easily
        self.styleObserverTags = []
        # keep a map of interactor styles to sliceWidgets so we can easily get sliceLogic
        self.sliceWidgetsPerStyle = {}
        self.refreshObservers()

    def setupInputFrame(self, parent=None):
        if not parent:
            parent = self.layout
        self.bgMultiVolumeSelector = slicer.qMRMLNodeComboBox()
        self.bgMultiVolumeSelector.nodeTypes = ['vtkMRMLMultiVolumeNode']
        self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
        self.bgMultiVolumeSelector.addEnabled = 0
        self._bgMultiVolumeSelectorLabel = QLabel('Input multivolume')
        inputFrameWidget = QWidget()
        self.inputFrameLayout = QFormLayout()
        inputFrameWidget.setLayout(self.inputFrameLayout)
        self.inputFrameLayout.addRow(self._bgMultiVolumeSelectorLabel,
                                     self.bgMultiVolumeSelector)
        parent.addWidget(inputFrameWidget)

    def setupFrameControlFrame(self):
        # TODO: initialize the slider based on the contents of the labels array
        self.frameSlider = ctk.ctkSliderWidget()
        self.frameSlider.setSizePolicy(QSizePolicy.Ignored,
                                       QSizePolicy.Preferred)
        self.frameLabel = QLabel('Current frame number')
        self.playButton = QPushButton('Play')
        self.playButton.toolTip = 'Iterate over multivolume frames'
        self.playButton.checkable = True
        frameControlHBox = QHBoxLayout()
        frameControlHBox.addWidget(self.frameLabel)
        frameControlHBox.addWidget(self.frameSlider)
        frameControlHBox.addWidget(self.playButton)
        self.inputFrameLayout.addRow(frameControlHBox)

    def setupAdditionalFrames(self):
        pass

    def setupPlottingFrame(self, parent=None):
        if not parent:
            parent = self.layout
        self.plottingFrameWidget = QWidget()
        self.plottingFrameLayout = QGridLayout()
        self.plottingFrameWidget.setLayout(self.plottingFrameLayout)
        self._multiVolumeIntensityChart = MultiVolumeIntensityChartView()
        self.popupChartButton = QPushButton("Undock chart")
        self.popupChartButton.setCheckable(True)
        self.plottingFrameLayout.addWidget(
            self._multiVolumeIntensityChart.chartView)
        self.plottingFrameLayout.addWidget(self.popupChartButton)
        parent.addWidget(self.plottingFrameWidget)

    def setupConnections(self):
        self.parent.connect('mrmlSceneChanged(vtkMRMLScene*)',
                            self.onVCMRMLSceneChanged)
        self.bgMultiVolumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                           self.onBackgroundInputChanged)
        self.playButton.connect('toggled(bool)', self.onPlayButtonToggled)
        self.frameSlider.connect('valueChanged(double)', self.onSliderChanged)
        self.timer.connect('timeout()', self.goToNext)
        self.popupChartButton.connect('toggled(bool)',
                                      self.onDockChartViewToggled)

    def onDockChartViewToggled(self, checked):
        if checked:
            self.chartPopupWindow = QDialog()
            self.chartPopupWindow.setWindowFlags(
                PythonQt.QtCore.Qt.WindowStaysOnTopHint)
            layout = QGridLayout()
            self.chartPopupWindow.setLayout(layout)
            layout.addWidget(self._multiVolumeIntensityChart.chartView)
            layout.addWidget(self.popupChartButton)
            self.chartPopupWindow.finished.connect(self.dockChartView)
            self.chartPopupWindow.resize(self.chartPopupSize)
            self.chartPopupWindow.move(self.chartPopupPosition)
            self.chartPopupWindow.show()
            self.popupChartButton.setText("Dock chart")
            self._multiVolumeIntensityChart.chartView.show()
        else:
            self.chartPopupWindow.close()

    def dockChartView(self):
        self.chartPopupSize = self.chartPopupWindow.size
        self.chartPopupPosition = self.chartPopupWindow.pos
        self.plottingFrameLayout.addWidget(
            self._multiVolumeIntensityChart.chartView)
        self.plottingFrameLayout.addWidget(self.popupChartButton)
        self.popupChartButton.setText("Undock chart")
        self.popupChartButton.disconnect('toggled(bool)',
                                         self.onDockChartViewToggled)
        self.popupChartButton.checked = False
        self.popupChartButton.connect('toggled(bool)',
                                      self.onDockChartViewToggled)

    def onSliderChanged(self, frameId):
        if self._bgMultiVolumeNode is None:
            return
        newValue = int(frameId)
        self.setCurrentFrameNumber(newValue)

    def onVCMRMLSceneChanged(self, mrmlScene):
        logging.debug(
            "qSlicerMultiVolumeExplorerSimplifiedModuleWidget:onVCMRMLSceneChanged"
        )
        self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
        self.onBackgroundInputChanged()

    def refreshGUIForNewBackgroundImage(self):
        self._multiVolumeIntensityChart.reset()
        self.setFramesEnabled(True)
        if self._fgMultiVolumeNode and self._bgMultiVolumeNode:
            Helper.SetBgFgVolumes(self._bgMultiVolumeNode.GetID(),
                                  self._fgMultiVolumeNode.GetID())
        else:
            Helper.SetBgVolume(self._bgMultiVolumeNode.GetID())
        self.refreshFrameSlider()
        self._multiVolumeIntensityChart.bgMultiVolumeNode = self._bgMultiVolumeNode
        self.refreshObservers()

    def getBackgroundMultiVolumeNode(self):
        return self.bgMultiVolumeSelector.currentNode()

    def onBackgroundInputChanged(self):
        self._bgMultiVolumeNode = self.getBackgroundMultiVolumeNode()

        if self._bgMultiVolumeNode is not None:
            self.refreshGUIForNewBackgroundImage()
        else:
            self.setFramesEnabled(False)

    def onPlayButtonToggled(self, checked):
        if self._bgMultiVolumeNode is None:
            return
        if checked:
            self.timer.start()
            self.playButton.text = 'Stop'
        else:
            self.timer.stop()
            self.playButton.text = 'Play'

    def processEvent(self, observee, event):
        # logging.debug("processing event %s" % event)
        if self._bgMultiVolumeNode is None:
            return

        # TODO: use a timer to delay calculation and compress events
        if event == 'LeaveEvent':
            # reset all the readouts
            # TODO: reset the label text
            return

        if not self.sliceWidgetsPerStyle.has_key(observee):
            return

        interactor = observee.GetInteractor()
        self.createChart(self.sliceWidgetsPerStyle[observee],
                         interactor.GetEventPosition())

    def createChart(self, sliceWidget, position):
        self._multiVolumeIntensityChart.createChart(sliceWidget, position)

    def setCurrentFrameNumber(self, frameNumber):
        mvDisplayNode = self._bgMultiVolumeNode.GetDisplayNode()
        mvDisplayNode.SetFrameComponent(frameNumber)

    def setFramesEnabled(self, enabled):
        pass

    def refreshObservers(self):
        """ When the layout changes, drop the observers from
    all the old widgets and create new observers for the
    newly created widgets"""
        self.removeObservers()
        # get new slice nodes
        layoutManager = slicer.app.layoutManager()
        sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass(
            'vtkMRMLSliceNode')
        for nodeIndex in xrange(sliceNodeCount):
            # find the widget for each node in scene
            sliceNode = slicer.mrmlScene.GetNthNodeByClass(
                nodeIndex, 'vtkMRMLSliceNode')
            sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName())
            if sliceWidget:
                # add observers and keep track of tags
                style = sliceWidget.sliceView().interactorStyle()
                self.sliceWidgetsPerStyle[style] = sliceWidget
                events = ("MouseMoveEvent", "EnterEvent", "LeaveEvent")
                for event in events:
                    tag = style.AddObserver(event, self.processEvent)
                    self.styleObserverTags.append([style, tag])

    def removeObservers(self):
        for observee, tag in self.styleObserverTags:
            observee.RemoveObserver(tag)
        self.styleObserverTags = []
        self.sliceWidgetsPerStyle = {}

    def refreshFrameSlider(self):
        self.frameSlider.minimum = 0
        if not self._bgMultiVolumeNode:
            self.frameSlider.maximum = 0
            return
        nFrames = self._bgMultiVolumeNode.GetNumberOfFrames()
        self.frameSlider.maximum = nFrames - 1

    def goToNext(self):
        currentElement = self.frameSlider.value
        currentElement += 1
        if currentElement > self.frameSlider.maximum:
            currentElement = 0
        self.frameSlider.value = currentElement
class qSlicerMultiVolumeExplorerSimplifiedModuleWidget:

  def __init__(self, parent=None):
    logging.debug("qSlicerMultiVolumeExplorerSimplifiedModuleWidget:init() called")
    if not parent or not hasattr(parent, "layout"):
      self.parent = slicer.qMRMLWidget()
      self.parent.setLayout(QVBoxLayout())
    else:
      self.parent = parent

    self.layout = self.parent.layout()

    self._bgMultiVolumeNode = None
    self._fgMultiVolumeNode = None

    self.styleObserverTags = []
    self.sliceWidgetsPerStyle = {}

    self.chartPopupWindow = None
    self.chartPopupSize = QSize(600, 300)
    self.chartPopupPosition = QPoint(0,0)

  def hide(self):
    self.widget.hide()

  def show(self):
    self.widget.show()

  def setup(self):
    self.widget = QWidget()
    layout = QGridLayout()
    self.widget.setLayout(layout)
    self.layout.addWidget(self.widget)
    self.widget.show()
    self.layout = layout

    self.setupInputFrame()
    self.setupFrameControlFrame()
    self.setupAdditionalFrames()
    self.setupPlottingFrame()

    self.setFramesEnabled(False)

    self.timer = QTimer()
    self.timer.setInterval(50)

    self.setupConnections()

    # initialize slice observers (from DataProbe.py)
    # keep list of pairs: [observee,tag] so they can be removed easily
    self.styleObserverTags = []
    # keep a map of interactor styles to sliceWidgets so we can easily get sliceLogic
    self.sliceWidgetsPerStyle = {}
    self.refreshObservers()

  def setupInputFrame(self, parent=None):
    if not parent:
      parent = self.layout
    self.bgMultiVolumeSelector = slicer.qMRMLNodeComboBox()
    self.bgMultiVolumeSelector.nodeTypes = ['vtkMRMLMultiVolumeNode']
    self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
    self.bgMultiVolumeSelector.addEnabled = 0
    self._bgMultiVolumeSelectorLabel = QLabel('Input multivolume')
    inputFrameWidget = QWidget()
    self.inputFrameLayout = QFormLayout()
    inputFrameWidget.setLayout(self.inputFrameLayout)
    self.inputFrameLayout.addRow(self._bgMultiVolumeSelectorLabel, self.bgMultiVolumeSelector)
    parent.addWidget(inputFrameWidget)

  def setupFrameControlFrame(self):
    # TODO: initialize the slider based on the contents of the labels array
    self.frameSlider = ctk.ctkSliderWidget()
    self.frameLabel = QLabel('Current frame number')
    self.playButton = QPushButton('Play')
    self.playButton.toolTip = 'Iterate over multivolume frames'
    self.playButton.checkable = True
    frameControlHBox = QHBoxLayout()
    frameControlHBox.addWidget(self.frameLabel)
    frameControlHBox.addWidget(self.frameSlider)
    frameControlHBox.addWidget(self.playButton)
    self.inputFrameLayout.addRow(frameControlHBox)

  def setupAdditionalFrames(self):
    pass

  def setupPlottingFrame(self, parent=None):
    if not parent:
      parent = self.layout
    self.plottingFrameWidget = QWidget()
    self.plottingFrameLayout = QGridLayout()
    self.plottingFrameWidget.setLayout(self.plottingFrameLayout)
    self._multiVolumeIntensityChart = MultiVolumeIntensityChartView()
    self.popupChartButton = QPushButton("Undock chart")
    self.popupChartButton.setCheckable(True)
    self.plottingFrameLayout.addWidget(self._multiVolumeIntensityChart.chartView)
    self.plottingFrameLayout.addWidget(self.popupChartButton)
    parent.addWidget(self.plottingFrameWidget)

  def setupConnections(self):
    self.parent.connect('mrmlSceneChanged(vtkMRMLScene*)', self.onVCMRMLSceneChanged)
    self.bgMultiVolumeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onBackgroundInputChanged)
    self.playButton.connect('toggled(bool)', self.onPlayButtonToggled)
    self.frameSlider.connect('valueChanged(double)', self.onSliderChanged)
    self.timer.connect('timeout()', self.goToNext)
    self.popupChartButton.connect('toggled(bool)', self.onDockChartViewToggled)

  def onDockChartViewToggled(self, checked):
    if checked:
      self.chartPopupWindow = QDialog()
      self.chartPopupWindow.setWindowFlags(PythonQt.QtCore.Qt.WindowStaysOnTopHint)
      layout = QGridLayout()
      self.chartPopupWindow.setLayout(layout)
      layout.addWidget(self._multiVolumeIntensityChart.chartView)
      layout.addWidget(self.popupChartButton)
      self.chartPopupWindow.finished.connect(self.dockChartView)
      self.chartPopupWindow.resize(self.chartPopupSize)
      self.chartPopupWindow.move(self.chartPopupPosition)
      self.chartPopupWindow.show()
      self.popupChartButton.setText("Dock chart")
      self._multiVolumeIntensityChart.chartView.show()
    else:
      self.chartPopupWindow.close()

  def dockChartView(self):
    self.chartPopupSize = self.chartPopupWindow.size
    self.chartPopupPosition = self.chartPopupWindow.pos
    self.plottingFrameLayout.addWidget(self._multiVolumeIntensityChart.chartView)
    self.plottingFrameLayout.addWidget(self.popupChartButton)
    self.popupChartButton.setText("Undock chart")
    self.popupChartButton.disconnect('toggled(bool)', self.onDockChartViewToggled)
    self.popupChartButton.checked = False
    self.popupChartButton.connect('toggled(bool)', self.onDockChartViewToggled)

  def onSliderChanged(self, frameId):
    if self._bgMultiVolumeNode is None:
      return
    newValue = int(frameId)
    self.setCurrentFrameNumber(newValue)

  def onVCMRMLSceneChanged(self, mrmlScene):
    logging.debug("qSlicerMultiVolumeExplorerSimplifiedModuleWidget:onVCMRMLSceneChanged")
    self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
    self.onBackgroundInputChanged()

  def refreshGUIForNewBackgroundImage(self):
    self._multiVolumeIntensityChart.reset()
    self.setFramesEnabled(True)
    if self._fgMultiVolumeNode and self._bgMultiVolumeNode:
      Helper.SetBgFgVolumes(self._bgMultiVolumeNode.GetID(), self._fgMultiVolumeNode.GetID())
    else:
      Helper.SetBgVolume(self._bgMultiVolumeNode.GetID())
    self.refreshFrameSlider()
    self._multiVolumeIntensityChart.bgMultiVolumeNode = self._bgMultiVolumeNode
    self.refreshObservers()

  def getBackgroundMultiVolumeNode(self):
    return self.bgMultiVolumeSelector.currentNode()

  def onBackgroundInputChanged(self):
    self._bgMultiVolumeNode = self.getBackgroundMultiVolumeNode()

    if self._bgMultiVolumeNode is not None:
      self.refreshGUIForNewBackgroundImage()
    else:
      self.setFramesEnabled(False)

  def onPlayButtonToggled(self, checked):
    if self._bgMultiVolumeNode is None:
      return
    if checked:
      self.timer.start()
      self.playButton.text = 'Stop'
    else:
      self.timer.stop()
      self.playButton.text = 'Play'

  def processEvent(self, observee, event):
    # logging.debug("processing event %s" % event)
    if self._bgMultiVolumeNode is None:
      return

    # TODO: use a timer to delay calculation and compress events
    if event == 'LeaveEvent':
      # reset all the readouts
      # TODO: reset the label text
      return

    if not self.sliceWidgetsPerStyle.has_key(observee):
      return

    interactor = observee.GetInteractor()
    self.createChart(self.sliceWidgetsPerStyle[observee], interactor.GetEventPosition())

  def createChart(self, sliceWidget, position):
    self._multiVolumeIntensityChart.createChart(sliceWidget, position)

  def setCurrentFrameNumber(self, frameNumber):
    mvDisplayNode = self._bgMultiVolumeNode.GetDisplayNode()
    mvDisplayNode.SetFrameComponent(frameNumber)

  def setFramesEnabled(self, enabled):
    pass

  def refreshObservers(self):
    """ When the layout changes, drop the observers from
    all the old widgets and create new observers for the
    newly created widgets"""
    self.removeObservers()
    # get new slice nodes
    layoutManager = slicer.app.layoutManager()
    sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode')
    for nodeIndex in xrange(sliceNodeCount):
      # find the widget for each node in scene
      sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode')
      sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName())
      if sliceWidget:
        # add observers and keep track of tags
        style = sliceWidget.sliceView().interactorStyle()
        self.sliceWidgetsPerStyle[style] = sliceWidget
        events = ("MouseMoveEvent", "EnterEvent", "LeaveEvent")
        for event in events:
          tag = style.AddObserver(event, self.processEvent)
          self.styleObserverTags.append([style,tag])

  def removeObservers(self):
    for observee,tag in self.styleObserverTags:
      observee.RemoveObserver(tag)
    self.styleObserverTags = []
    self.sliceWidgetsPerStyle = {}

  def refreshFrameSlider(self):
    self.frameSlider.minimum = 0
    if not self._bgMultiVolumeNode:
      self.frameSlider.maximum = 0
      return
    nFrames = self._bgMultiVolumeNode.GetNumberOfFrames()
    self.frameSlider.maximum = nFrames - 1

  def goToNext(self):
    currentElement = self.frameSlider.value
    currentElement += 1
    if currentElement > self.frameSlider.maximum:
      currentElement = 0
    self.frameSlider.value = currentElement
class ProstateTRUSNavUltrasound(UltraSound):

    OFFLINE_VOLUME_FILENAME = "RecVol_Reference.mha"
    SCOUT_VOLUME_FILENAME = "ScoutScan.mha"
    LIVE_VOLUME_FILENAME = "LiveReconstructedVolume.mha"

    RECORDING_FILENAME = "Recording.mha"
    SCOUT_RECORDING_FILENAME = "ScoutScanRecording.mha"
    LIVE_RECORDING_FILENAME = LIVE_VOLUME_FILENAME

    SCOUT_VOLUME_NODE_NAME = "ScoutScan"
    OFFLINE_VOLUME_NODE_NAME = "RecVol_Reference"
    LIVE_VOLUME_NODE_NAME = "liveReconstruction"

    APPLY_HOLE_FILLING_FOR_SNAPSHOT = False
    SNAPSHOT_INTERVAL = 3
    OUTPUT_VOLUME_SPACING = 3
    LIVE_OUTPUT_VOLUME_SPACING = 1

    @property
    def roiNode(self):
        return self._roiNode

    @roiNode.setter
    def roiNode(self, node):
        self._roiNode = node
        if node is not None:
            self.startStopLiveReconstructionButton.setEnabled(True)
        else:
            self.startStopLiveReconstructionButton.setEnabled(False)

    def __init__(self, guideletParent):
        UltraSound.__init__(self, guideletParent)

        self.parameterNode = guideletParent.parameterNode
        self.parameterNodeObserver = None
        self._roiNode = None
        self.liveOutputSpacingValue = [
            self.LIVE_OUTPUT_VOLUME_SPACING, self.LIVE_OUTPUT_VOLUME_SPACING,
            self.LIVE_OUTPUT_VOLUME_SPACING
        ]
        self.outputSpacing = [
            self.OUTPUT_VOLUME_SPACING, self.OUTPUT_VOLUME_SPACING,
            self.OUTPUT_VOLUME_SPACING
        ]
        self.roiOrigin = None
        self.roiExtent = None
        self.defaultParameterNode = None
        self.logic = ProstateTRUSNavUltrasoundLogic()

    def setupPanel(self, parentWidget):
        logging.debug('ProstateTRUSNavUltrasound.setupPanel')

        self.connectorNode = self.guideletParent.connectorNode
        self.connectorNodeConnected = False

        collapsibleButton = ctkCollapsibleButton()
        collapsibleButton.setProperty('collapsedHeight', 20)
        setButtonStyle(collapsibleButton, 2.0)
        collapsibleButton.text = "Ultrasound"
        parentWidget.addWidget(collapsibleButton)

        ultrasoundLayout = QFormLayout(collapsibleButton)
        ultrasoundLayout.setContentsMargins(12, 4, 4, 4)
        ultrasoundLayout.setSpacing(4)

        self.connectDisconnectButton = QPushButton("Connect")
        self.connectDisconnectButton.setToolTip(
            "If clicked, connection OpenIGTLink")

        hbox = QHBoxLayout()
        hbox.addWidget(self.connectDisconnectButton)
        ultrasoundLayout.addRow(hbox)

        self.setupIcons()

        self.captureIDSelector = QComboBox()
        self.captureIDSelector.setToolTip("Pick capture device ID")
        self.captureIDSelector.setSizePolicy(QSizePolicy.Expanding,
                                             QSizePolicy.Expanding)

        self.volumeReconstructorIDSelector = QComboBox()
        self.volumeReconstructorIDSelector.setToolTip(
            "Pick volume reconstructor device ID")
        self.volumeReconstructorIDSelector.setSizePolicy(
            QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.startStopRecordingButton = QPushButton("  Start Recording")
        self.startStopRecordingButton.setCheckable(True)
        self.startStopRecordingButton.setIcon(self.recordIcon)
        self.startStopRecordingButton.setEnabled(False)
        self.startStopRecordingButton.setToolTip("If clicked, start recording")
        self.startStopRecordingButton.setSizePolicy(QSizePolicy.Expanding,
                                                    QSizePolicy.Expanding)

        recordParametersControlsLayout = QGridLayout()

        self.filenameLabel = self.createLabel("Filename:", visible=False)
        recordParametersControlsLayout.addWidget(self.filenameLabel, 1, 0)

        # Offline Reconstruction
        self.offlineReconstructButton = QPushButton("  Offline Reconstruction")
        self.offlineReconstructButton.setCheckable(True)
        self.offlineReconstructButton.setIcon(self.recordIcon)
        self.offlineReconstructButton.setEnabled(False)
        self.offlineReconstructButton.setToolTip(
            "If clicked, reconstruct recorded volume")
        self.offlineReconstructButton.setSizePolicy(QSizePolicy.Expanding,
                                                    QSizePolicy.Expanding)

        self.offlineVolumeToReconstructSelector = QComboBox()
        self.offlineVolumeToReconstructSelector.setEditable(True)
        self.offlineVolumeToReconstructSelector.setToolTip(
            "Pick/set volume to reconstruct")
        self.offlineVolumeToReconstructSelector.visible = False

        hbox = QHBoxLayout()
        hbox.addWidget(self.startStopRecordingButton)
        hbox.addWidget(self.offlineReconstructButton)
        ultrasoundLayout.addRow(hbox)

        # Scout scan (record and low resolution reconstruction) and live reconstruction
        # Scout scan part

        self.startStopScoutScanButton = QPushButton(
            "  Scout scan\n  Start recording")
        self.startStopScoutScanButton.setCheckable(True)
        self.startStopScoutScanButton.setIcon(self.recordIcon)
        self.startStopScoutScanButton.setToolTip("If clicked, start recording")
        self.startStopScoutScanButton.setEnabled(False)
        self.startStopScoutScanButton.setSizePolicy(QSizePolicy.Expanding,
                                                    QSizePolicy.Expanding)

        self.startStopLiveReconstructionButton = QPushButton(
            "  Start live reconstruction")
        self.startStopLiveReconstructionButton.setCheckable(True)
        self.startStopLiveReconstructionButton.setIcon(self.recordIcon)
        self.startStopLiveReconstructionButton.setToolTip(
            "If clicked, start live reconstruction")
        self.startStopLiveReconstructionButton.setEnabled(False)
        self.startStopLiveReconstructionButton.setSizePolicy(
            QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.displayRoiButton = QToolButton()
        self.displayRoiButton.setCheckable(True)
        self.displayRoiButton.setIcon(self.visibleOffIcon)
        self.displayRoiButton.setToolTip("If clicked, display ROI")

        hbox = QHBoxLayout()
        hbox.addWidget(self.startStopScoutScanButton)
        hbox.addWidget(self.startStopLiveReconstructionButton)
        # hbox.addWidget(self.displayRoiButton)
        ultrasoundLayout.addRow(hbox)

        self.snapshotTimer = QTimer()
        self.snapshotTimer.setSingleShot(True)

        self.onParameterSetSelected()

        return collapsibleButton

    def setupResliceDriver(self):
        layoutManager = slicer.app.layoutManager()
        # Show ultrasound in red view.
        redSlice = layoutManager.sliceWidget('Red')
        redSliceLogic = redSlice.sliceLogic()
        redSliceLogic.GetSliceCompositeNode().SetBackgroundVolumeID(
            self.liveUltrasoundNode_Reference.GetID())

        resliceLogic = slicer.modules.volumereslicedriver.logic()
        if resliceLogic:
            redNode = slicer.util.getNode('vtkMRMLSliceNodeRed')
            redNode.SetSliceResolutionMode(
                slicer.vtkMRMLSliceNode.SliceResolutionMatchVolumes)
            resliceLogic.SetDriverForSlice(
                self.liveUltrasoundNode_Reference.GetID(), redNode)
            resliceLogic.SetModeForSlice(
                6, redNode)  # Transverse mode, default for PLUS ultrasound.
        else:
            logging.warning('Logic not found for Volume Reslice Driver')

    def createCollapsibleButton(self, text, collapsed=False):
        collapsibleButton = ctkCollapsibleButton()
        collapsibleButton.text = text
        collapsibleButton.collapsed = collapsed
        return collapsibleButton

    def createLabel(self, text, visible=True):
        label = QLabel()
        label.setText(text)
        label.visible = visible
        return label

    def setupConnections(self):
        self.startStopRecordingButton.connect(
            'clicked(bool)', self.onStartStopRecordingButtonClicked)
        self.offlineReconstructButton.connect('clicked(bool)',
                                              self.onReconstVolume)
        self.startStopScoutScanButton.connect(
            'clicked(bool)', self.onStartStopScoutScanButtonClicked)
        self.startStopLiveReconstructionButton.connect(
            'clicked(bool)', self.onStartStopLiveReconstructionButtonClicked)
        self.displayRoiButton.connect('clicked(bool)',
                                      self.onDisplayRoiButtonClicked)
        self.captureIDSelector.connect('currentIndexChanged(QString)',
                                       self.updateParameterNodeFromGui)
        self.volumeReconstructorIDSelector.connect(
            'currentIndexChanged(QString)', self.updateParameterNodeFromGui)
        self.offlineVolumeToReconstructSelector.connect(
            'currentIndexChanged(int)', self.updateParameterNodeFromGui)
        self.displayRoiButton.connect('clicked(bool)',
                                      self.updateParameterNodeFromGui)
        self.snapshotTimer.timeout.connect(
            self.onRequestVolumeReconstructionSnapshot)
        self.connectDisconnectButton.connect(
            'clicked(bool)', self.onConnectDisconnectButtonClicked)

    def disconnect(self):
        self.startStopRecordingButton.disconnect(
            'clicked(bool)', self.onStartStopRecordingButtonClicked)
        self.offlineReconstructButton.disconnect('clicked(bool)',
                                                 self.onReconstVolume)
        self.startStopScoutScanButton.disconnect(
            'clicked(bool)', self.onStartStopScoutScanButtonClicked)
        self.startStopLiveReconstructionButton.disconnect(
            'clicked(bool)', self.onStartStopLiveReconstructionButtonClicked)
        self.displayRoiButton.disconnect('clicked(bool)',
                                         self.onDisplayRoiButtonClicked)
        self.captureIDSelector.disconnect('currentIndexChanged(QString)',
                                          self.updateParameterNodeFromGui)
        self.volumeReconstructorIDSelector.disconnect(
            'currentIndexChanged(QString)', self.updateParameterNodeFromGui)
        self.offlineVolumeToReconstructSelector.disconnect(
            'currentIndexChanged(int)', self.updateParameterNodeFromGui)
        self.displayRoiButton.disconnect('clicked(bool)',
                                         self.updateParameterNodeFromGui)
        self.snapshotTimer.timeout.disconnect(
            self.onRequestVolumeReconstructionSnapshot)
        self.connectDisconnectButton.disconnect(
            'clicked(bool)', self.onConnectDisconnectButtonClicked)

    def setupIcons(self):
        self.plusRemoteModuleDirectoryPath = slicer.modules.plusremote.path.replace(
            "PlusRemote.py", "")
        self.recordIcon = QIcon(self.plusRemoteModuleDirectoryPath +
                                '/Resources/Icons/icon_Record.png')
        self.stopIcon = QIcon(self.plusRemoteModuleDirectoryPath +
                              '/Resources/Icons/icon_Stop.png')
        self.waitIcon = QIcon(self.plusRemoteModuleDirectoryPath +
                              '/Resources/Icons/icon_Wait.png')
        self.visibleOffIcon = QIcon(":Icons\VisibleOff.png")
        self.visibleOnIcon = QIcon(":Icons\VisibleOn.png")

    def onParameterSetSelected(self):
        # Set up default values for new nodes
        if self.parameterNode:
            self.plusRemoteLogic.setDefaultParameters(self.parameterNode)
        self.updateGuiFromParameterNode()

    def updateGuiFromParameterNode(self):

        self.parameterVolumeList = {
            'OfflineVolumeToReconstruct':
            self.offlineVolumeToReconstructSelector
        }
        for parameter in self.parameterVolumeList:
            if self.parameterNode.GetParameter(parameter):
                self.parameterVolumeList[parameter].blockSignals(True)
            self.parameterVolumeList[parameter].blockSignals(False)

        if self.parameterNode.GetParameter('CaptureID'):
            self.captureIDSelector.blockSignals(True)
            for i in range(0, self.captureIDSelector.count):
                if self.parameterNode.GetParameter(
                        'CaptureID') == self.captureIDSelector.itemText(i):
                    self.captureIDSelector.setCurrentIndex(
                        int(self.parameterNode.GetParameter('CaptureIdIndex')))
            self.captureIDSelector.blockSignals(False)

        if self.parameterNode.GetParameter('VolumeReconstructor'):
            self.volumeReconstructorIDSelector.blockSignals(True)
            for i in range(0, self.volumeReconstructorIDSelector.count):
                if self.parameterNode.GetParameter(
                        'VolumeReconstructor'
                ) == self.volumeReconstructorIDSelector.itemText(i):
                    self.volumeReconstructorIDSelector.setCurrentIndex(
                        int(
                            self.parameterNode.GetParameter(
                                'VolumeReconstructorIndex')))
            self.volumeReconstructorIDSelector.blockSignals(False)

            self.roiNode = self.parameterNode.GetNthNodeReference('ROI', 0)

    def updateParameterNodeFromGui(self):
        #Update parameter node value to save when user change value in the interface
        if not self.parameterNode:
            return
        self.parametersList = {
            'CaptureID':
            self.captureIDSelector.currentText,
            'CaptureIdIndex':
            self.captureIDSelector.currentIndex,
            'VolumeReconstructor':
            self.volumeReconstructorIDSelector.currentText,
            'VolumeReconstructorIndex':
            self.volumeReconstructorIDSelector.currentIndex,
            'OfflineVolumeToReconstruct':
            self.offlineVolumeToReconstructSelector.currentIndex
        }
        for parameter in self.parametersList:
            self.parameterNode.SetParameter(
                parameter, str(self.parametersList[parameter]))
        if self.roiNode:
            roiNodeID = self.roiNode.GetID()
            self.parameterNode.SetNthNodeReferenceID('ROI', 0, roiNodeID)


#
# Connector observation and actions
#

    def onConnectorNodeConnected(self):
        logging.debug("ProstateTrusUltrasound:onConnectorNodeConnected")
        self.connectorNodeConnected = True
        self.captureIDSelector.setDisabled(False)
        self.volumeReconstructorIDSelector.setDisabled(False)
        self.plusRemoteLogic.getCaptureDeviceIds(
            self.connectorNode.GetID(),
            self.onGetCaptureDeviceCommandResponseReceived)
        self.plusRemoteLogic.getVolumeReconstructorDeviceIds(
            self.connectorNode.GetID(),
            self.onGetVolumeReconstructorDeviceCommandResponseReceived)
        self.connectDisconnectButton.setText("Disconnect")

    def onConnectorNodeDisconnected(self):
        logging.debug("ProstateTrusUltrasound:onConnectorNodeDisconnected")
        self.connectorNodeConnected = False
        self.startStopRecordingButton.setEnabled(False)
        self.startStopScoutScanButton.setEnabled(False)
        self.startStopLiveReconstructionButton.setEnabled(False)
        self.offlineReconstructButton.setEnabled(False)
        self.captureIDSelector.setDisabled(True)
        self.volumeReconstructorIDSelector.setDisabled(True)
        self.connectDisconnectButton.setText("Connect")

    def getLiveVolumeRecNode(self):
        liveVolumeRecNode = slicer.util.getNode(self.LIVE_VOLUME_NODE_NAME)
        return liveVolumeRecNode

    def getOfflineVolumeRecNode(self):
        offlineVolumeRecNode = slicer.util.getNode(
            self.OFFLINE_VOLUME_NODE_NAME)
        return offlineVolumeRecNode

    def getScoutVolumeNode(self):
        scoutScanVolumeNode = slicer.util.getNode(self.SCOUT_VOLUME_NODE_NAME)
        return scoutScanVolumeNode

    def onConnectDisconnectButtonClicked(self):
        if self.connectorNode.GetState(
        ) == slicer.vtkMRMLIGTLConnectorNode.STATE_CONNECTED:
            self.connectorNode.Stop()
        else:
            self.connectorNode.Start()

    def onStartStopRecordingButtonClicked(self):
        if self.startStopRecordingButton.isChecked():
            self.startStopRecordingButton.setText("  Stop Recording")
            self.startStopRecordingButton.setIcon(self.stopIcon)
            self.startStopRecordingButton.setToolTip(
                "If clicked, stop recording")
            self.onStartRecording(self.generateRecordingOutputFilename())
        else:
            self.startStopRecordingButton.setText("  Start Recording")
            self.startStopRecordingButton.setIcon(self.recordIcon)
            self.startStopRecordingButton.setToolTip(
                "If clicked, start recording")
            self.onStopRecording(self.onVolumeRecorded)

    def onStartStopScoutScanButtonClicked(self):
        if self.startStopScoutScanButton.isChecked():
            self.startStopScoutScanButton.setText(
                "  Scout Scan\n  Stop Recording and Reconstruct Recorded Volume"
            )
            self.startStopScoutScanButton.setIcon(self.stopIcon)
            self.startStopScoutScanButton.setToolTip(
                "If clicked, stop recording and reconstruct recorded volume")
            self.onStartRecording(self.generateScoutRecordingOutputFilename())
        else:
            self.onStopRecording(self.onScoutVolumeRecorded)

    def onStartStopLiveReconstructionButtonClicked(self):
        if self.startStopLiveReconstructionButton.isChecked():
            if self.roiNode:
                self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(
                    self.LIVE_OUTPUT_VOLUME_SPACING, self.roiNode)
            self.startStopLiveReconstructionButton.setText(
                "  Stop Live Reconstruction")
            self.startStopLiveReconstructionButton.setIcon(self.stopIcon)
            self.startStopLiveReconstructionButton.setToolTip(
                "If clicked, stop live reconstruction")
            self.onStartRecording(self.getLiveRecordingOutputFilename())
            self.onStartReconstruction()
        else:
            self.startStopLiveReconstructionButton.setText(
                "  Start Live Reconstruction")
            self.startStopLiveReconstructionButton.setIcon(self.recordIcon)
            self.startStopLiveReconstructionButton.setToolTip(
                "If clicked, start live reconstruction")
            self.onStopRecording(self.printCommandResponse)
            self.onStopReconstruction()

    def onDisplayRoiButtonClicked(self):
        if self.displayRoiButton.isChecked():
            self.displayRoiButton.setIcon(self.visibleOnIcon)
            self.displayRoiButton.setToolTip("If clicked, hide ROI")
            if self.roiNode:
                self.roiNode.SetDisplayVisibility(1)
        else:
            self.displayRoiButton.setIcon(self.visibleOffIcon)
            self.displayRoiButton.setToolTip("If clicked, display ROI")
            if self.roiNode:
                self.roiNode.SetDisplayVisibility(0)

    def generateRecordingOutputFilename(self):
        return self.plusRemoteLogic.addTimestampToFilename(
            self.RECORDING_FILENAME)

    def generateScoutRecordingOutputFilename(self):
        return self.plusRemoteLogic.addTimestampToFilename(
            self.SCOUT_RECORDING_FILENAME)

    def getLiveRecordingOutputFilename(self):
        return self.plusRemoteLogic.addTimestampToFilename(
            self.LIVE_RECORDING_FILENAME)

    def getLiveReconstructionOutputFilename(self):
        return self.plusRemoteLogic.addTimestampToFilename(
            self.LIVE_VOLUME_FILENAME)

    def onStartRecording(self, filename):
        self.plusRemoteLogic.startRecording(self.connectorNode.GetID(),
                                            self.captureIDSelector.currentText,
                                            filename,
                                            self.printCommandResponse)

    def onStopRecording(self, callback):
        self.plusRemoteLogic.stopRecording(self.connectorNode.GetID(),
                                           self.captureIDSelector.currentText,
                                           callback)

    def onStartReconstruction(self):
        if self.roiNode:
            self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(
                self.LIVE_OUTPUT_VOLUME_SPACING, self.roiNode)
        self.plusRemoteLogic.startVolumeReconstuction(
            self.connectorNode.GetID(),
            self.volumeReconstructorIDSelector.currentText,
            self.liveOutputSpacingValue, self.roiOrigin, self.roiExtent,
            self.printCommandResponse,
            self.getLiveReconstructionOutputFilename(),
            self.LIVE_VOLUME_NODE_NAME)
        # Set up timer for requesting snapshot
        self.snapshotTimer.start(self.SNAPSHOT_INTERVAL * 1000)

    def onStopReconstruction(self):
        self.snapshotTimer.stop()
        self.plusRemoteLogic.stopVolumeReconstruction(
            self.connectorNode.GetID(),
            self.volumeReconstructorIDSelector.currentText,
            self.onVolumeLiveReconstructed,
            self.getLiveReconstructionOutputFilename(),
            self.LIVE_VOLUME_NODE_NAME)

    def onReconstVolume(self):
        self.offlineReconstructButton.setIcon(self.waitIcon)
        self.offlineReconstructButton.setText(
            "  Offline Reconstruction in progress ...")
        self.offlineReconstructButton.setEnabled(False)
        self.plusRemoteLogic.reconstructRecorded(
            self.connectorNode.GetID(),
            self.volumeReconstructorIDSelector.currentText,
            self.offlineVolumeToReconstructSelector.currentText,
            self.outputSpacing, self.onVolumeReconstructed,
            self.OFFLINE_VOLUME_FILENAME, self.OFFLINE_VOLUME_NODE_NAME)

    def onScoutScanReconstVolume(self):
        self.startStopScoutScanButton.setIcon(self.waitIcon)
        self.startStopScoutScanButton.setText(
            "  Scout Scan\n  Reconstruction in progress ...")
        self.startStopScoutScanButton.setEnabled(False)
        self.plusRemoteLogic.reconstructRecorded(
            self.connectorNode.GetID(),
            self.volumeReconstructorIDSelector.currentText,
            self.lastScoutRecordingOutputFilename, self.outputSpacing,
            self.onScoutVolumeReconstructed, self.SCOUT_VOLUME_FILENAME,
            self.SCOUT_VOLUME_NODE_NAME)

    def onRequestVolumeReconstructionSnapshot(self, stopFlag=""):
        self.plusRemoteLogic.getVolumeReconstructionSnapshot(
            self.connectorNode.GetID(),
            self.volumeReconstructorIDSelector.currentText,
            self.LIVE_VOLUME_FILENAME, self.LIVE_VOLUME_NODE_NAME,
            self.APPLY_HOLE_FILLING_FOR_SNAPSHOT, self.onSnapshotAcquired)

    def executeCommandDelayed(self, method, delay=100):
        # Order of OpenIGTLink message receiving and processing is not guaranteed to be the same
        # therefore we wait a bit to make sure the image message is processed as well
        QTimer.singleShot(delay, method)

    def printCommandResponse(self, command, q):
        statusText = "Command {0} [{1}]: {2}\n".format(
            command.GetCommandName(), command.GetID(),
            command.StatusToString(command.GetStatus()))
        if command.GetResponseMessage():
            statusText = statusText + command.GetResponseMessage()
        elif command.GetResponseText():
            statusText = statusText + command.GetResponseText()
        logging.debug(statusText)

    def onGetCaptureDeviceCommandResponseReceived(self, command, q):
        self.printCommandResponse(command, q)
        if command.GetStatus() != command.CommandSuccess:
            return

        captureDeviceIdsListString = command.GetResponseMessage()
        if captureDeviceIdsListString:
            captureDevicesIdsList = captureDeviceIdsListString.split(",")
        else:
            captureDevicesIdsList = []

        for i in range(0, len(captureDevicesIdsList)):
            if self.captureIDSelector.findText(captureDevicesIdsList[i]) == -1:
                self.captureIDSelector.addItem(captureDevicesIdsList[i])

    def onGetVolumeReconstructorDeviceCommandResponseReceived(
            self, command, q):
        self.printCommandResponse(command, q)
        if command.GetStatus() != command.CommandSuccess:
            return

        volumeReconstructorDeviceIdsListString = command.GetResponseMessage()
        if volumeReconstructorDeviceIdsListString:
            volumeReconstructorDeviceIdsList = volumeReconstructorDeviceIdsListString.split(
                ",")
        else:
            volumeReconstructorDeviceIdsList = []

        self.volumeReconstructorIDSelector.clear()
        self.volumeReconstructorIDSelector.addItems(
            volumeReconstructorDeviceIdsList)
        self.startStopRecordingButton.setEnabled(True)
        self.offlineReconstructButton.setEnabled(True)
        self.startStopScoutScanButton.setEnabled(True)
        if self.roiNode:
            self.startStopLiveReconstructionButton.setEnabled(True)

    def onVolumeRecorded(self, command, q):
        self.printCommandResponse(command, q)
        self.offlineReconstructButton.setEnabled(True)

        volumeToReconstructFileName = os.path.basename(
            command.GetResponseMessage())
        self.offlineVolumeToReconstructSelector.insertItem(
            0, volumeToReconstructFileName)
        self.offlineVolumeToReconstructSelector.setCurrentIndex(0)

    def onScoutVolumeRecorded(self, command, q):
        self.printCommandResponse(command, q)
        self.offlineReconstructButton.setEnabled(True)

        if command.GetStatus() == command.CommandExpired:
            logging.fatal(
                "Scout Volume Recording: Timeout while waiting for volume reconstruction result"
            )
            return

        if command.GetStatus() == command.CommandSuccess:
            self.lastScoutRecordingOutputFilename = os.path.basename(
                command.GetResponseMessage())
            self.onScoutScanReconstVolume()

    def onVolumeReconstructed(self, command, q):
        self.printCommandResponse(command, q)

        self.offlineReconstructButton.setIcon(self.recordIcon)
        self.offlineReconstructButton.setText("Offline Reconstruction")
        self.offlineReconstructButton.setEnabled(True)
        self.offlineReconstructButton.setChecked(False)

        if command.GetStatus() == command.CommandExpired:
            # volume reconstruction command timed out
            logging.fatal(
                "Volume Reconstruction: Timeout while waiting for volume reconstruction result"
            )
            return

        if command.GetStatus() != command.CommandSuccess:
            logging.debug("Volume Reconstruction: " +
                          command.GetResponseMessage())
            return

        self.executeCommandDelayed(self.onVolumeReconstructedFinalize)

    def onVolumeReconstructedFinalize(self):
        applicationLogic = slicer.app.applicationLogic()
        applicationLogic.FitSliceToAll()
        self.guideletParent.showVolumeRendering(self.getOfflineVolumeRecNode())

    def onScoutVolumeReconstructed(self, command, q):
        self.printCommandResponse(command, q)

        if command.GetStatus() == command.CommandExpired:
            logging.fatal(
                "Scout Volume Reconstruction: Timeout while waiting for scout volume reconstruction result"
            )
            return

        self.startStopScoutScanButton.setIcon(self.recordIcon)
        self.startStopScoutScanButton.setText(
            "  Scout Scan\n  Start Recording")
        self.startStopScoutScanButton.setEnabled(True)

        if command.GetStatus() != command.CommandSuccess:
            logging.debug("Scout Volume Reconstruction: " +
                          command.GetResponseMessage())
            return

        self.executeCommandDelayed(self.onScoutVolumeReconstructedFinalize)

    def onScoutVolumeReconstructedFinalize(self):
        # Create and initialize ROI after scout scan because low resolution scout scan is used to set
        # a smaller ROI for the live high resolution reconstruction
        self.roiNode = self.logic.onRoiInitialization(
            self.SCOUT_VOLUME_NODE_NAME, self.roiNode)
        self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(
            self.LIVE_OUTPUT_VOLUME_SPACING, self.roiNode)
        scoutScanVolumeNode = self.getScoutVolumeNode()

        applicationLogic = slicer.app.applicationLogic()
        applicationLogic.FitSliceToAll()

        self.guideletParent.showVolumeRendering(scoutScanVolumeNode)

    def onSnapshotAcquired(self, command, q):
        self.printCommandResponse(command, q)

        if not self.startStopLiveReconstructionButton.isChecked():
            # live volume reconstruction is not active
            return

        self.executeCommandDelayed(self.onSnapshotAcquiredFinalize)

    def onSnapshotAcquiredFinalize(self):
        self.guideletParent.showVolumeRendering(self.getLiveVolumeRecNode())
        self.snapshotTimer.start(self.SNAPSHOT_INTERVAL * 1000)

    def onVolumeLiveReconstructed(self, command, q):
        self.printCommandResponse(command, q)

        if command.GetStatus() == command.CommandExpired:
            logging.fatal(
                "LIVE Volume Reconstruction: Failed to stop volume reconstruction"
            )
            return

        if command.GetStatus() != command.CommandSuccess:
            logging.debug("LIVE Volume Reconstruction " +
                          command.GetResponseMessage())
            return

        self.executeCommandDelayed(self.getLiveVolumeRecNode)

    def onVolumeLiveReconstructedFinalize(self):
        self.guideletParent.showVolumeRendering(self.getLiveVolumeRecNode())
class ProstateTRUSNavWidget(GuideletWidget):

    DEFAULT_PLUSSERVER_CHOOSER_TEXT = "Choose PlusServer.exe"
    DEFAULT_CONFIGURATION_CHOOSER_TEXT = "Select Configuration"

    def __init__(self, parent=None):
        GuideletWidget.__init__(self, parent)
        self.plusServerProcess = None
        self.configurationFile = self.getSetting(
            'ConfigurationFile', self.DEFAULT_CONFIGURATION_CHOOSER_TEXT)
        self.serverExecutable = self.getSetting(
            'PlusServer', self.DEFAULT_PLUSSERVER_CHOOSER_TEXT)

    def cleanup(self):
        GuideletWidget.cleanup(self)
        if self.plusServerProcess:
            self.plusServerProcess.terminate()

    def setup(self):
        showPlusServerWidget = True
        if _platform == "linux" or _platform == "linux2" or _platform == "darwin":  #linux or linux or OS X
            message = "Attention: You are running Slicer on Linux or OS X. Do you have PlusServer installed on the current OS?"
            result = QMessageBox.question(slicer.util.mainWindow(),
                                          'ProstateTRUSNav', message,
                                          QMessageBox.Yes | QMessageBox.No)
            showPlusServerWidget = result == QMessageBox.Yes

        if _platform == "win32" or showPlusServerWidget:
            # Windows...
            plusServerCollapsibleButton = ctkCollapsibleButton()
            plusServerCollapsibleButton.text = "PlusServer"
            self.layout.addWidget(plusServerCollapsibleButton)
            self.configurationFileChooserButton = QPushButton(
                self.configurationFile)
            self.configurationFileChooserButton.connect(
                'clicked()', self.onConfigFileSelected)
            self.runPlusServerButton = QPushButton("Run PlusServer")
            self.runPlusServerButton.setCheckable(True)
            self.runPlusServerButton.connect('clicked()',
                                             self.onRunPlusServerButtonClicked)

            self.serverFormLayout = QFormLayout(plusServerCollapsibleButton)

            self.serverExecutableChooserButton = QPushButton(
                self.serverExecutable)
            self.serverExecutableChooserButton.connect(
                'clicked()', self.onServerExecutableSelected)

            hbox = QHBoxLayout()
            hbox.addWidget(self.serverExecutableChooserButton)
            self.serverFormLayout.addRow(hbox)

            hbox = QHBoxLayout()
            hbox.addWidget(self.configurationFileChooserButton)
            hbox.addWidget(self.runPlusServerButton)
            self.serverFormLayout.addRow(hbox)

        GuideletWidget.setup(self)

        # do specific setup here
        if _platform == "win32" or showPlusServerWidget:
            self.launchGuideletButton.setEnabled(False)
            self.checkExecutableAndArgument()

    def checkExecutableAndArgument(self):
        if os.path.exists(self.serverExecutable) and os.path.exists(
                self.configurationFile):
            self.runPlusServerButton.setEnabled(True)
        else:
            self.runPlusServerButton.setEnabled(False)

    def getSetting(self, settingName, defaultValue=""):
        settings = QSettings()
        value = settings.value(self.moduleName + '/' + settingName,
                               defaultValue)
        return value if value is not None and value != "" else defaultValue

    def setSetting(self, settingName, value):
        settings = QSettings()
        settings.setValue(self.moduleName + '/' + settingName, value)

    def addLauncherWidgets(self):
        GuideletWidget.addLauncherWidgets(self)
        # add launcher widget here

    def onServerExecutableSelected(self):
        executable = QFileDialog.getOpenFileName(self.parent,
                                                 "PlusServer Executable",
                                                 self.serverExecutable,
                                                 "*.exe")
        if executable != "" and executable.find("PlusServer.exe"):
            self.serverExecutable = executable
            self.serverExecutableChooserButton.setText(executable)
            self.setSetting("PlusServer", executable)
        self.checkExecutableAndArgument()

    def onConfigFileSelected(self):
        self.configurationFile = QFileDialog.getOpenFileName(
            self.parent, "Choose Configuration File", self.configurationFile,
            "*.xml")
        if self.configurationFile != "":
            self.configurationFileChooserButton.setText(
                os.path.split(self.configurationFile)[1])
            self.setSetting("ConfigurationFile", self.configurationFile)
        self.checkExecutableAndArgument()

    def onRunPlusServerButtonClicked(self):
        if self.runPlusServerButton.isChecked():
            command = [
                self.serverExecutable,
                "--config-file=" + self.configurationFile
            ]
            logging.info("Executing %s %s" % tuple(command))
            self.plusServerProcess = Popen([
                self.serverExecutable,
                "--config-file=" + self.configurationFile
            ])
            if self.plusServerProcess:
                self.runPlusServerButton.setText("Quit Plus Server")
                self.launchGuideletButton.setEnabled(True)
        else:
            if self.plusServerProcess:
                self.plusServerProcess.terminate()
                self.runPlusServerButton.setText("Run PlusServer")
                self.launchGuideletButton.setEnabled(False)

    def collectParameterList(self):
        parameterList = GuideletWidget.collectParameterList(self)
        if not parameterList:
            parameterList = dict()
        parameterList['OfflineVolumeToReconstruct'] = 0,
        return parameterList

    def createGuideletInstance(self, parameterList=None):
        return ProstateTRUSNavGuidelet(None, self.guideletLogic, parameterList)

    def createGuideletLogic(self):
        return ProstateTRUSNavLogic()
class ProstateTRUSNavWidget(GuideletWidget):

  DEFAULT_PLUSSERVER_CHOOSER_TEXT = "Choose PlusServer.exe"
  DEFAULT_CONFIGURATION_CHOOSER_TEXT = "Select Configuration"

  def __init__(self, parent = None):
    GuideletWidget.__init__(self, parent)
    self.plusServerProcess = None
    self.configurationFile = self.getSetting('ConfigurationFile', self.DEFAULT_CONFIGURATION_CHOOSER_TEXT)
    self.serverExecutable = self.getSetting('PlusServer', self.DEFAULT_PLUSSERVER_CHOOSER_TEXT)

  def cleanup(self):
    GuideletWidget.cleanup(self)
    if self.plusServerProcess:
      self.plusServerProcess.terminate()

  def setup(self):
    showPlusServerWidget = True
    if _platform == "linux" or _platform == "linux2" or _platform == "darwin": #linux or linux or OS X
      message = "Attention: You are running Slicer on Linux or OS X. Do you have PlusServer installed on the current OS?"
      result = QMessageBox.question(slicer.util.mainWindow(), 'ProstateTRUSNav', message,
                                     QMessageBox.Yes | QMessageBox.No)
      showPlusServerWidget = result == QMessageBox.Yes

    if _platform == "win32" or showPlusServerWidget:
      # Windows...
      plusServerCollapsibleButton = ctkCollapsibleButton()
      plusServerCollapsibleButton.text = "PlusServer"
      self.layout.addWidget(plusServerCollapsibleButton)
      self.configurationFileChooserButton = QPushButton(self.configurationFile)
      self.configurationFileChooserButton.connect('clicked()', self.onConfigFileSelected)
      self.runPlusServerButton = QPushButton("Run PlusServer")
      self.runPlusServerButton.setCheckable(True)
      self.runPlusServerButton.connect('clicked()', self.onRunPlusServerButtonClicked)

      self.serverFormLayout = QFormLayout(plusServerCollapsibleButton)

      self.serverExecutableChooserButton = QPushButton(self.serverExecutable)
      self.serverExecutableChooserButton.connect('clicked()', self.onServerExecutableSelected)

      hbox = QHBoxLayout()
      hbox.addWidget(self.serverExecutableChooserButton)
      self.serverFormLayout.addRow(hbox)

      hbox = QHBoxLayout()
      hbox.addWidget(self.configurationFileChooserButton)
      hbox.addWidget(self.runPlusServerButton)
      self.serverFormLayout.addRow(hbox)

    GuideletWidget.setup(self)

    # do specific setup here
    if _platform == "win32" or showPlusServerWidget:
      self.launchGuideletButton.setEnabled(False)
      self.checkExecutableAndArgument()

  def checkExecutableAndArgument(self):
    if os.path.exists(self.serverExecutable) and os.path.exists(self.configurationFile):
      self.runPlusServerButton.setEnabled(True)
    else:
      self.runPlusServerButton.setEnabled(False)

  def getSetting(self, settingName, defaultValue=""):
    settings = QSettings()
    value = settings.value(self.moduleName + '/' + settingName, defaultValue)
    return value if value is not None and value != "" else defaultValue

  def setSetting(self, settingName, value):
    settings = QSettings()
    settings.setValue(self.moduleName + '/'+ settingName, value)

  def addLauncherWidgets(self):
    GuideletWidget.addLauncherWidgets(self)
    # add launcher widget here

  def onServerExecutableSelected(self):
    executable = QFileDialog.getOpenFileName(self.parent, "PlusServer Executable",
                                                self.serverExecutable, "*.exe")
    if executable != "" and executable.find("PlusServer.exe"):
      self.serverExecutable = executable
      self.serverExecutableChooserButton.setText(executable)
      self.setSetting("PlusServer", executable)
    self.checkExecutableAndArgument()

  def onConfigFileSelected(self):
    self.configurationFile = QFileDialog.getOpenFileName(self.parent, "Choose Configuration File",
                                                            self.configurationFile, "*.xml")
    if self.configurationFile != "":
      self.configurationFileChooserButton.setText(os.path.split(self.configurationFile)[1])
      self.setSetting("ConfigurationFile", self.configurationFile)
    self.checkExecutableAndArgument()

  def onRunPlusServerButtonClicked(self):
    if self.runPlusServerButton.isChecked():
      command = [self.serverExecutable, "--config-file="+self.configurationFile]
      logging.info("Executing %s %s" % tuple(command))
      self.plusServerProcess = Popen([self.serverExecutable, "--config-file="+self.configurationFile])
      if self.plusServerProcess:
        self.runPlusServerButton.setText("Quit Plus Server")
        self.launchGuideletButton.setEnabled(True)
    else:
      if self.plusServerProcess:
        self.plusServerProcess.terminate()
        self.runPlusServerButton.setText("Run PlusServer")
        self.launchGuideletButton.setEnabled(False)

  def collectParameterList(self):
    parameterList = GuideletWidget.collectParameterList(self)
    if not parameterList:
      parameterList = dict()
    parameterList['OfflineVolumeToReconstruct'] = 0,
    return parameterList

  def createGuideletInstance(self, parameterList = None):
    return ProstateTRUSNavGuidelet(None, self.guideletLogic,  parameterList)

  def createGuideletLogic(self):
    return ProstateTRUSNavLogic()
class ProstateTRUSNavUltrasound(UltraSound):

  OFFLINE_VOLUME_FILENAME = "RecVol_Reference.mha"
  SCOUT_VOLUME_FILENAME = "ScoutScan.mha"
  LIVE_VOLUME_FILENAME = "LiveReconstructedVolume.mha"

  RECORDING_FILENAME = "Recording.mha"
  SCOUT_RECORDING_FILENAME = "ScoutScanRecording.mha"
  LIVE_RECORDING_FILENAME = LIVE_VOLUME_FILENAME

  SCOUT_VOLUME_NODE_NAME = "ScoutScan"
  OFFLINE_VOLUME_NODE_NAME = "RecVol_Reference"
  LIVE_VOLUME_NODE_NAME = "liveReconstruction"

  APPLY_HOLE_FILLING_FOR_SNAPSHOT = False
  SNAPSHOT_INTERVAL = 3
  OUTPUT_VOLUME_SPACING = 3
  LIVE_OUTPUT_VOLUME_SPACING = 1

  @property
  def roiNode(self):
    return self._roiNode

  @roiNode.setter
  def roiNode(self, node):
    self._roiNode=node
    if node is not None:
      self.startStopLiveReconstructionButton.setEnabled(True)
    else:
      self.startStopLiveReconstructionButton.setEnabled(False)

  def __init__(self, guideletParent):
    UltraSound.__init__(self, guideletParent)

    self.parameterNode = guideletParent.parameterNode
    self.parameterNodeObserver = None
    self._roiNode = None
    self.liveOutputSpacingValue = [self.LIVE_OUTPUT_VOLUME_SPACING, self.LIVE_OUTPUT_VOLUME_SPACING,
                                   self.LIVE_OUTPUT_VOLUME_SPACING]
    self.outputSpacing = [self.OUTPUT_VOLUME_SPACING, self.OUTPUT_VOLUME_SPACING, self.OUTPUT_VOLUME_SPACING]
    self.roiOrigin = None
    self.roiExtent = None
    self.defaultParameterNode = None
    self.logic = ProstateTRUSNavUltrasoundLogic()

  def setupPanel(self, parentWidget):
    logging.debug('ProstateTRUSNavUltrasound.setupPanel')

    self.connectorNode = self.guideletParent.connectorNode
    self.connectorNodeConnected = False

    collapsibleButton = ctkCollapsibleButton()
    collapsibleButton.setProperty('collapsedHeight', 20)
    setButtonStyle(collapsibleButton, 2.0)
    collapsibleButton.text = "Ultrasound"
    parentWidget.addWidget(collapsibleButton)

    ultrasoundLayout = QFormLayout(collapsibleButton)
    ultrasoundLayout.setContentsMargins(12,4,4,4)
    ultrasoundLayout.setSpacing(4)

    self.connectDisconnectButton = QPushButton("Connect")
    self.connectDisconnectButton.setToolTip("If clicked, connection OpenIGTLink")

    hbox = QHBoxLayout()
    hbox.addWidget(self.connectDisconnectButton)
    ultrasoundLayout.addRow(hbox)

    self.setupIcons()

    self.captureIDSelector = QComboBox()
    self.captureIDSelector.setToolTip("Pick capture device ID")
    self.captureIDSelector.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.volumeReconstructorIDSelector = QComboBox()
    self.volumeReconstructorIDSelector.setToolTip( "Pick volume reconstructor device ID" )
    self.volumeReconstructorIDSelector.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.startStopRecordingButton = QPushButton("  Start Recording")
    self.startStopRecordingButton.setCheckable(True)
    self.startStopRecordingButton.setIcon(self.recordIcon)
    self.startStopRecordingButton.setEnabled(False)
    self.startStopRecordingButton.setToolTip("If clicked, start recording")
    self.startStopRecordingButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    recordParametersControlsLayout = QGridLayout()

    self.filenameLabel = self.createLabel("Filename:", visible=False)
    recordParametersControlsLayout.addWidget(self.filenameLabel, 1, 0)

     # Offline Reconstruction
    self.offlineReconstructButton = QPushButton("  Offline Reconstruction")
    self.offlineReconstructButton.setCheckable(True)
    self.offlineReconstructButton.setIcon(self.recordIcon)
    self.offlineReconstructButton.setEnabled(False)
    self.offlineReconstructButton.setToolTip("If clicked, reconstruct recorded volume")
    self.offlineReconstructButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.offlineVolumeToReconstructSelector = QComboBox()
    self.offlineVolumeToReconstructSelector.setEditable(True)
    self.offlineVolumeToReconstructSelector.setToolTip( "Pick/set volume to reconstruct" )
    self.offlineVolumeToReconstructSelector.visible = False

    hbox = QHBoxLayout()
    hbox.addWidget(self.startStopRecordingButton)
    hbox.addWidget(self.offlineReconstructButton)
    ultrasoundLayout.addRow(hbox)

    # Scout scan (record and low resolution reconstruction) and live reconstruction
    # Scout scan part

    self.startStopScoutScanButton = QPushButton("  Scout scan\n  Start recording")
    self.startStopScoutScanButton.setCheckable(True)
    self.startStopScoutScanButton.setIcon(self.recordIcon)
    self.startStopScoutScanButton.setToolTip("If clicked, start recording")
    self.startStopScoutScanButton.setEnabled(False)
    self.startStopScoutScanButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.startStopLiveReconstructionButton = QPushButton("  Start live reconstruction")
    self.startStopLiveReconstructionButton.setCheckable(True)
    self.startStopLiveReconstructionButton.setIcon(self.recordIcon)
    self.startStopLiveReconstructionButton.setToolTip("If clicked, start live reconstruction")
    self.startStopLiveReconstructionButton.setEnabled(False)
    self.startStopLiveReconstructionButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.displayRoiButton = QToolButton()
    self.displayRoiButton.setCheckable(True)
    self.displayRoiButton.setIcon(self.visibleOffIcon)
    self.displayRoiButton.setToolTip("If clicked, display ROI")

    hbox = QHBoxLayout()
    hbox.addWidget(self.startStopScoutScanButton)
    hbox.addWidget(self.startStopLiveReconstructionButton)
    # hbox.addWidget(self.displayRoiButton)
    ultrasoundLayout.addRow(hbox)

    self.snapshotTimer = QTimer()
    self.snapshotTimer.setSingleShot(True)

    self.onParameterSetSelected()

    return collapsibleButton

  def setupResliceDriver(self):
    layoutManager = slicer.app.layoutManager()
    # Show ultrasound in red view.
    redSlice = layoutManager.sliceWidget('Red')
    redSliceLogic = redSlice.sliceLogic()
    redSliceLogic.GetSliceCompositeNode().SetBackgroundVolumeID(self.liveUltrasoundNode_Reference.GetID())

    resliceLogic = slicer.modules.volumereslicedriver.logic()
    if resliceLogic:
      redNode = slicer.util.getNode('vtkMRMLSliceNodeRed')
      redNode.SetSliceResolutionMode(slicer.vtkMRMLSliceNode.SliceResolutionMatchVolumes)
      resliceLogic.SetDriverForSlice(self.liveUltrasoundNode_Reference.GetID(), redNode)
      resliceLogic.SetModeForSlice(6, redNode) # Transverse mode, default for PLUS ultrasound.
    else:
      logging.warning('Logic not found for Volume Reslice Driver')

  def createCollapsibleButton(self, text, collapsed=False):
    collapsibleButton = ctkCollapsibleButton()
    collapsibleButton.text = text
    collapsibleButton.collapsed = collapsed
    return collapsibleButton

  def createLabel(self, text, visible=True):
    label = QLabel()
    label.setText(text)
    label.visible = visible
    return label

  def setupConnections(self):
    self.startStopRecordingButton.connect('clicked(bool)', self.onStartStopRecordingButtonClicked)
    self.offlineReconstructButton.connect('clicked(bool)', self.onReconstVolume)
    self.startStopScoutScanButton.connect('clicked(bool)', self.onStartStopScoutScanButtonClicked)
    self.startStopLiveReconstructionButton.connect('clicked(bool)', self.onStartStopLiveReconstructionButtonClicked)
    self.displayRoiButton.connect('clicked(bool)', self.onDisplayRoiButtonClicked)
    self.captureIDSelector.connect('currentIndexChanged(QString)', self.updateParameterNodeFromGui)
    self.volumeReconstructorIDSelector.connect('currentIndexChanged(QString)', self.updateParameterNodeFromGui)
    self.offlineVolumeToReconstructSelector.connect('currentIndexChanged(int)', self.updateParameterNodeFromGui)
    self.displayRoiButton.connect('clicked(bool)', self.updateParameterNodeFromGui)
    self.snapshotTimer.timeout.connect(self.onRequestVolumeReconstructionSnapshot)
    self.connectDisconnectButton.connect('clicked(bool)', self.onConnectDisconnectButtonClicked)

  def disconnect(self):
    self.startStopRecordingButton.disconnect('clicked(bool)', self.onStartStopRecordingButtonClicked)
    self.offlineReconstructButton.disconnect('clicked(bool)', self.onReconstVolume)
    self.startStopScoutScanButton.disconnect('clicked(bool)', self.onStartStopScoutScanButtonClicked)
    self.startStopLiveReconstructionButton.disconnect('clicked(bool)', self.onStartStopLiveReconstructionButtonClicked)
    self.displayRoiButton.disconnect('clicked(bool)', self.onDisplayRoiButtonClicked)
    self.captureIDSelector.disconnect('currentIndexChanged(QString)', self.updateParameterNodeFromGui)
    self.volumeReconstructorIDSelector.disconnect('currentIndexChanged(QString)', self.updateParameterNodeFromGui)
    self.offlineVolumeToReconstructSelector.disconnect('currentIndexChanged(int)', self.updateParameterNodeFromGui)
    self.displayRoiButton.disconnect('clicked(bool)', self.updateParameterNodeFromGui)
    self.snapshotTimer.timeout.disconnect(self.onRequestVolumeReconstructionSnapshot)
    self.connectDisconnectButton.disconnect('clicked(bool)', self.onConnectDisconnectButtonClicked)

  def setupIcons(self):
    self.plusRemoteModuleDirectoryPath = slicer.modules.plusremote.path.replace("PlusRemote.py", "")
    self.recordIcon = QIcon(self.plusRemoteModuleDirectoryPath + '/Resources/Icons/icon_Record.png')
    self.stopIcon = QIcon(self.plusRemoteModuleDirectoryPath + '/Resources/Icons/icon_Stop.png')
    self.waitIcon = QIcon(self.plusRemoteModuleDirectoryPath + '/Resources/Icons/icon_Wait.png')
    self.visibleOffIcon = QIcon(":Icons\VisibleOff.png")
    self.visibleOnIcon = QIcon(":Icons\VisibleOn.png")

  def onParameterSetSelected(self):
    # Set up default values for new nodes
    if self.parameterNode:
      self.plusRemoteLogic.setDefaultParameters(self.parameterNode)
    self.updateGuiFromParameterNode()

  def updateGuiFromParameterNode(self):

    self.parameterVolumeList = {'OfflineVolumeToReconstruct': self.offlineVolumeToReconstructSelector}
    for parameter in self.parameterVolumeList:
      if self.parameterNode.GetParameter(parameter):
        self.parameterVolumeList[parameter].blockSignals(True)
      self.parameterVolumeList[parameter].blockSignals(False)

    if self.parameterNode.GetParameter('CaptureID'):
      self.captureIDSelector.blockSignals(True)
      for i in range(0, self.captureIDSelector.count):
        if self.parameterNode.GetParameter('CaptureID') == self.captureIDSelector.itemText(i):
          self.captureIDSelector.setCurrentIndex(int(self.parameterNode.GetParameter('CaptureIdIndex')))
      self.captureIDSelector.blockSignals(False)

    if self.parameterNode.GetParameter('VolumeReconstructor'):
      self.volumeReconstructorIDSelector.blockSignals(True)
      for i in range(0, self.volumeReconstructorIDSelector.count):
        if self.parameterNode.GetParameter('VolumeReconstructor') == self.volumeReconstructorIDSelector.itemText(i):
          self.volumeReconstructorIDSelector.setCurrentIndex(int(self.parameterNode.GetParameter('VolumeReconstructorIndex')))
      self.volumeReconstructorIDSelector.blockSignals(False)

      self.roiNode = self.parameterNode.GetNthNodeReference('ROI', 0)

  def updateParameterNodeFromGui(self):
    #Update parameter node value to save when user change value in the interface
    if not self.parameterNode:
      return
    self.parametersList = {'CaptureID': self.captureIDSelector.currentText,
                           'CaptureIdIndex': self.captureIDSelector.currentIndex,
                           'VolumeReconstructor': self.volumeReconstructorIDSelector.currentText,
                           'VolumeReconstructorIndex': self.volumeReconstructorIDSelector.currentIndex,
                           'OfflineVolumeToReconstruct': self.offlineVolumeToReconstructSelector.currentIndex}
    for parameter in self.parametersList:
      self.parameterNode.SetParameter(parameter, str(self.parametersList[parameter]))
    if self.roiNode:
      roiNodeID = self.roiNode.GetID()
      self.parameterNode.SetNthNodeReferenceID('ROI', 0, roiNodeID)

#
# Connector observation and actions
#

  def onConnectorNodeConnected(self):
    logging.debug("ProstateTrusUltrasound:onConnectorNodeConnected")
    self.connectorNodeConnected = True
    self.captureIDSelector.setDisabled(False)
    self.volumeReconstructorIDSelector.setDisabled(False)
    self.plusRemoteLogic.getCaptureDeviceIds(self.connectorNode.GetID(),
                                   self.onGetCaptureDeviceCommandResponseReceived)
    self.plusRemoteLogic.getVolumeReconstructorDeviceIds(self.connectorNode.GetID(),
                                               self.onGetVolumeReconstructorDeviceCommandResponseReceived)
    self.connectDisconnectButton.setText("Disconnect")

  def onConnectorNodeDisconnected(self):
    logging.debug("ProstateTrusUltrasound:onConnectorNodeDisconnected")
    self.connectorNodeConnected = False
    self.startStopRecordingButton.setEnabled(False)
    self.startStopScoutScanButton.setEnabled(False)
    self.startStopLiveReconstructionButton.setEnabled(False)
    self.offlineReconstructButton.setEnabled(False)
    self.captureIDSelector.setDisabled(True)
    self.volumeReconstructorIDSelector.setDisabled(True)
    self.connectDisconnectButton.setText("Connect")

  def getLiveVolumeRecNode(self):
    liveVolumeRecNode = slicer.util.getNode(self.LIVE_VOLUME_NODE_NAME)
    return liveVolumeRecNode

  def getOfflineVolumeRecNode(self):
    offlineVolumeRecNode = slicer.util.getNode(self.OFFLINE_VOLUME_NODE_NAME)
    return offlineVolumeRecNode

  def getScoutVolumeNode(self):
    scoutScanVolumeNode = slicer.util.getNode(self.SCOUT_VOLUME_NODE_NAME)
    return scoutScanVolumeNode

  def onConnectDisconnectButtonClicked(self):
    if self.connectorNode.GetState() == slicer.vtkMRMLIGTLConnectorNode.STATE_CONNECTED:
      self.connectorNode.Stop()
    else:
      self.connectorNode.Start()

  def onStartStopRecordingButtonClicked(self):
    if self.startStopRecordingButton.isChecked():
      self.startStopRecordingButton.setText("  Stop Recording")
      self.startStopRecordingButton.setIcon(self.stopIcon)
      self.startStopRecordingButton.setToolTip( "If clicked, stop recording" )
      self.onStartRecording(self.generateRecordingOutputFilename())
    else:
      self.startStopRecordingButton.setText("  Start Recording")
      self.startStopRecordingButton.setIcon(self.recordIcon)
      self.startStopRecordingButton.setToolTip( "If clicked, start recording" )
      self.onStopRecording(self.onVolumeRecorded)

  def onStartStopScoutScanButtonClicked(self):
    if self.startStopScoutScanButton.isChecked():
      self.startStopScoutScanButton.setText("  Scout Scan\n  Stop Recording and Reconstruct Recorded Volume")
      self.startStopScoutScanButton.setIcon(self.stopIcon)
      self.startStopScoutScanButton.setToolTip( "If clicked, stop recording and reconstruct recorded volume" )
      self.onStartRecording(self.generateScoutRecordingOutputFilename())
    else:
      self.onStopRecording(self.onScoutVolumeRecorded)

  def onStartStopLiveReconstructionButtonClicked(self):
    if self.startStopLiveReconstructionButton.isChecked():
      if self.roiNode:
        self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(self.LIVE_OUTPUT_VOLUME_SPACING,
                                                                                       self.roiNode)
      self.startStopLiveReconstructionButton.setText("  Stop Live Reconstruction")
      self.startStopLiveReconstructionButton.setIcon(self.stopIcon)
      self.startStopLiveReconstructionButton.setToolTip( "If clicked, stop live reconstruction" )
      self.onStartRecording(self.getLiveRecordingOutputFilename())
      self.onStartReconstruction()
    else:
      self.startStopLiveReconstructionButton.setText("  Start Live Reconstruction")
      self.startStopLiveReconstructionButton.setIcon(self.recordIcon)
      self.startStopLiveReconstructionButton.setToolTip( "If clicked, start live reconstruction" )
      self.onStopRecording(self.printCommandResponse)
      self.onStopReconstruction()

  def onDisplayRoiButtonClicked(self):
    if self.displayRoiButton.isChecked():
      self.displayRoiButton.setIcon(self.visibleOnIcon)
      self.displayRoiButton.setToolTip("If clicked, hide ROI")
      if self.roiNode:
        self.roiNode.SetDisplayVisibility(1)
    else:
      self.displayRoiButton.setIcon(self.visibleOffIcon)
      self.displayRoiButton.setToolTip("If clicked, display ROI")
      if self.roiNode:
        self.roiNode.SetDisplayVisibility(0)

  def generateRecordingOutputFilename(self):
    return self.plusRemoteLogic.addTimestampToFilename(self.RECORDING_FILENAME)

  def generateScoutRecordingOutputFilename(self):
    return self.plusRemoteLogic.addTimestampToFilename(self.SCOUT_RECORDING_FILENAME)

  def getLiveRecordingOutputFilename(self):
    return self.plusRemoteLogic.addTimestampToFilename(self.LIVE_RECORDING_FILENAME)

  def getLiveReconstructionOutputFilename(self):
    return self.plusRemoteLogic.addTimestampToFilename(self.LIVE_VOLUME_FILENAME)

  def onStartRecording(self, filename):
    self.plusRemoteLogic.startRecording(self.connectorNode.GetID(), self.captureIDSelector.currentText,
                                        filename, self.printCommandResponse)

  def onStopRecording(self, callback):
    self.plusRemoteLogic.stopRecording(self.connectorNode.GetID(), self.captureIDSelector.currentText, callback)

  def onStartReconstruction(self):
    if self.roiNode:
      self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(self.LIVE_OUTPUT_VOLUME_SPACING,
                                                                                     self.roiNode)
    self.plusRemoteLogic.startVolumeReconstuction(self.connectorNode.GetID(),
                                                   self.volumeReconstructorIDSelector.currentText,
                                                   self.liveOutputSpacingValue, self.roiOrigin,
                                                   self.roiExtent, self.printCommandResponse,
                                                   self.getLiveReconstructionOutputFilename(), self.LIVE_VOLUME_NODE_NAME)
    # Set up timer for requesting snapshot
    self.snapshotTimer.start(self.SNAPSHOT_INTERVAL*1000)

  def onStopReconstruction(self):
    self.snapshotTimer.stop()
    self.plusRemoteLogic.stopVolumeReconstruction(self.connectorNode.GetID(),
                                                  self.volumeReconstructorIDSelector.currentText,
                                                  self.onVolumeLiveReconstructed,
                                                  self.getLiveReconstructionOutputFilename(),
                                                  self.LIVE_VOLUME_NODE_NAME)

  def onReconstVolume(self):
    self.offlineReconstructButton.setIcon(self.waitIcon)
    self.offlineReconstructButton.setText("  Offline Reconstruction in progress ...")
    self.offlineReconstructButton.setEnabled(False)
    self.plusRemoteLogic.reconstructRecorded(self.connectorNode.GetID(), self.volumeReconstructorIDSelector.currentText,
                                             self.offlineVolumeToReconstructSelector.currentText, self.outputSpacing,
                                             self.onVolumeReconstructed, self.OFFLINE_VOLUME_FILENAME,
                                             self.OFFLINE_VOLUME_NODE_NAME)

  def onScoutScanReconstVolume(self):
    self.startStopScoutScanButton.setIcon(self.waitIcon)
    self.startStopScoutScanButton.setText("  Scout Scan\n  Reconstruction in progress ...")
    self.startStopScoutScanButton.setEnabled(False)
    self.plusRemoteLogic.reconstructRecorded(self.connectorNode.GetID(), self.volumeReconstructorIDSelector.currentText,
                                             self.lastScoutRecordingOutputFilename, self.outputSpacing,
                                             self.onScoutVolumeReconstructed, self.SCOUT_VOLUME_FILENAME,
                                             self.SCOUT_VOLUME_NODE_NAME)

  def onRequestVolumeReconstructionSnapshot(self, stopFlag = ""):
    self.plusRemoteLogic.getVolumeReconstructionSnapshot(self.connectorNode.GetID(),
                                                         self.volumeReconstructorIDSelector.currentText,
                                                         self.LIVE_VOLUME_FILENAME,
                                                         self.LIVE_VOLUME_NODE_NAME,
                                                         self.APPLY_HOLE_FILLING_FOR_SNAPSHOT, self.onSnapshotAcquired)

  def executeCommandDelayed(self, method, delay=100):
    # Order of OpenIGTLink message receiving and processing is not guaranteed to be the same
    # therefore we wait a bit to make sure the image message is processed as well
    QTimer.singleShot(delay, method)

  def printCommandResponse(self, command, q):
    statusText = "Command {0} [{1}]: {2}\n".format(command.GetCommandName(), command.GetID(),
                                                   command.StatusToString(command.GetStatus()))
    if command.GetResponseMessage():
      statusText = statusText + command.GetResponseMessage()
    elif command.GetResponseText():
      statusText = statusText + command.GetResponseText()
    logging.debug(statusText)

  def onGetCaptureDeviceCommandResponseReceived(self, command, q):
    self.printCommandResponse(command, q)
    if command.GetStatus() != command.CommandSuccess:
      return

    captureDeviceIdsListString = command.GetResponseMessage()
    if captureDeviceIdsListString:
      captureDevicesIdsList = captureDeviceIdsListString.split(",")
    else:
      captureDevicesIdsList = []

    for i in range(0,len(captureDevicesIdsList)):
      if self.captureIDSelector.findText(captureDevicesIdsList[i]) == -1:
        self.captureIDSelector.addItem(captureDevicesIdsList[i])

  def onGetVolumeReconstructorDeviceCommandResponseReceived(self, command, q):
    self.printCommandResponse(command, q)
    if command.GetStatus() != command.CommandSuccess:
      return

    volumeReconstructorDeviceIdsListString = command.GetResponseMessage()
    if volumeReconstructorDeviceIdsListString:
      volumeReconstructorDeviceIdsList = volumeReconstructorDeviceIdsListString.split(",")
    else:
      volumeReconstructorDeviceIdsList = []

    self.volumeReconstructorIDSelector.clear()
    self.volumeReconstructorIDSelector.addItems(volumeReconstructorDeviceIdsList)
    self.startStopRecordingButton.setEnabled(True)
    self.offlineReconstructButton.setEnabled(True)
    self.startStopScoutScanButton.setEnabled(True)
    if self.roiNode:
      self.startStopLiveReconstructionButton.setEnabled(True)

  def onVolumeRecorded(self, command, q):
    self.printCommandResponse(command, q)
    self.offlineReconstructButton.setEnabled(True)

    volumeToReconstructFileName = os.path.basename(command.GetResponseMessage())
    self.offlineVolumeToReconstructSelector.insertItem(0,volumeToReconstructFileName)
    self.offlineVolumeToReconstructSelector.setCurrentIndex(0)

  def onScoutVolumeRecorded(self, command, q):
    self.printCommandResponse(command,q)
    self.offlineReconstructButton.setEnabled(True)

    if command.GetStatus() == command.CommandExpired:
      logging.fatal("Scout Volume Recording: Timeout while waiting for volume reconstruction result")
      return

    if command.GetStatus() == command.CommandSuccess:
      self.lastScoutRecordingOutputFilename = os.path.basename(command.GetResponseMessage())
      self.onScoutScanReconstVolume()

  def onVolumeReconstructed(self, command, q):
    self.printCommandResponse(command,q)

    self.offlineReconstructButton.setIcon(self.recordIcon)
    self.offlineReconstructButton.setText("Offline Reconstruction")
    self.offlineReconstructButton.setEnabled(True)
    self.offlineReconstructButton.setChecked(False)

    if command.GetStatus() == command.CommandExpired:
      # volume reconstruction command timed out
      logging.fatal("Volume Reconstruction: Timeout while waiting for volume reconstruction result")
      return

    if command.GetStatus() != command.CommandSuccess:
      logging.debug("Volume Reconstruction: " + command.GetResponseMessage())
      return

    self.executeCommandDelayed(self.onVolumeReconstructedFinalize)

  def onVolumeReconstructedFinalize(self):
    applicationLogic = slicer.app.applicationLogic()
    applicationLogic.FitSliceToAll()
    self.guideletParent.showVolumeRendering(self.getOfflineVolumeRecNode())

  def onScoutVolumeReconstructed(self, command, q):
    self.printCommandResponse(command,q)

    if command.GetStatus() == command.CommandExpired:
      logging.fatal("Scout Volume Reconstruction: Timeout while waiting for scout volume reconstruction result")
      return

    self.startStopScoutScanButton.setIcon(self.recordIcon)
    self.startStopScoutScanButton.setText("  Scout Scan\n  Start Recording")
    self.startStopScoutScanButton.setEnabled(True)

    if command.GetStatus() != command.CommandSuccess:
      logging.debug("Scout Volume Reconstruction: " + command.GetResponseMessage())
      return

    self.executeCommandDelayed(self.onScoutVolumeReconstructedFinalize)

  def onScoutVolumeReconstructedFinalize(self):
    # Create and initialize ROI after scout scan because low resolution scout scan is used to set
    # a smaller ROI for the live high resolution reconstruction
    self.roiNode = self.logic.onRoiInitialization(self.SCOUT_VOLUME_NODE_NAME, self.roiNode)
    self.roiOrigin, self.roiExtent = self.logic.updateVolumeOriginAndExtentFromROI(self.LIVE_OUTPUT_VOLUME_SPACING,
                                                                                   self.roiNode)
    scoutScanVolumeNode = self.getScoutVolumeNode()

    applicationLogic = slicer.app.applicationLogic()
    applicationLogic.FitSliceToAll()

    self.guideletParent.showVolumeRendering(scoutScanVolumeNode)

  def onSnapshotAcquired(self, command, q):
    self.printCommandResponse(command,q)

    if not self.startStopLiveReconstructionButton.isChecked():
      # live volume reconstruction is not active
      return

    self.executeCommandDelayed(self.onSnapshotAcquiredFinalize)

  def onSnapshotAcquiredFinalize(self):
    self.guideletParent.showVolumeRendering(self.getLiveVolumeRecNode())
    self.snapshotTimer.start(self.SNAPSHOT_INTERVAL*1000)

  def onVolumeLiveReconstructed(self, command, q):
    self.printCommandResponse(command,q)

    if command.GetStatus() == command.CommandExpired:
      logging.fatal("LIVE Volume Reconstruction: Failed to stop volume reconstruction")
      return

    if command.GetStatus() != command.CommandSuccess:
      logging.debug("LIVE Volume Reconstruction " + command.GetResponseMessage())
      return

    self.executeCommandDelayed(self.getLiveVolumeRecNode)

  def onVolumeLiveReconstructedFinalize(self):
    self.guideletParent.showVolumeRendering(self.getLiveVolumeRecNode())