def onRightClick(self):
     menu = qt.QMenu()
     position = qt.QCursor.pos()
     action = qt.QAction("Delete highlighted fiducial(s)", menu)
     menu.addAction(action)
     connectObject = qt.QObject()
     connectObject.connect(action, 'triggered()',
                           self.onDeleteOneFiducialButton)
     action2 = qt.QAction("Cancel", menu)
     menu.addAction(action2)
     connectObject.connect(action2, 'triggered()', menu.hide)
     menu.exec_(position)
Exemple #2
0
    def createButtonRow(self, effects, rowLabel=""):
        """ create a row of the edit box given a list of
    effect names (items in _effects(list) """

        rowFrame = qt.QFrame(self.mainFrame)
        self.mainFrame.layout().addWidget(rowFrame)
        self.rowFrames.append(rowFrame)
        hbox = qt.QHBoxLayout()
        rowFrame.setLayout(hbox)

        if rowLabel:
            label = qt.QLabel(rowLabel)
            hbox.addWidget(label)

        for effect in effects:
            # check that the effect belongs in our list of effects before including
            if (effect in self.effects):
                i = self.icons[effect] = qt.QIcon(self.effectIconFiles[effect])
                a = self.actions[effect] = qt.QAction(i, '', rowFrame)
                self.effectButtons[effect] = b = self.buttons[
                    effect] = qt.QToolButton()
                b.setDefaultAction(a)
                b.setToolTip(effect)
                if EditBox.displayNames.has_key(effect):
                    b.setToolTip(EditBox.displayNames[effect])
                hbox.addWidget(b)

                # Setup the mapping between button and its associated effect name
                self.effectMapper.setMapping(self.buttons[effect], effect)
                # Connect button with signal mapper
                self.buttons[effect].connect('clicked()', self.effectMapper,
                                             'map()')

        hbox.addStretch(1)
Exemple #3
0
    def createButtonRow(self, effects, rowLabel=""):

        f = qt.QFrame(self.parent)
        self.parent.layout().addWidget(f)
        self.rowFrames.append(f)
        hbox = qt.QHBoxLayout()
        f.setLayout(hbox)

        if rowLabel:
            label = qt.QLabel(rowLabel)
            hbox.addWidget(label)

        for effect in effects:
            # check that the effect belongs in our list of effects before including
            if (effect in self.effects):
                i = self.icons[effect] = qt.QIcon(
                    self.effectIconFiles[effect, self.effectModes[effect]])
                a = self.actions[effect] = qt.QAction(i, '', f)
                self.effectButtons[effect] = b = self.buttons[
                    effect] = qt.QToolButton()
                b.setDefaultAction(a)
                b.setToolTip(effect)
                if EditBox.displayNames.has_key(effect):
                    b.setToolTip(EditBox.displayNames[effect])
                hbox.addWidget(b)
                if self.disabled.__contains__(effect):
                    b.setDisabled(1)

                # Setup the mapping between button and its associated effect name
                self.effectMapper.setMapping(self.buttons[effect], effect)
                # Connect button with signal mapper
                self.buttons[effect].connect('clicked()', self.effectMapper,
                                             'map()')

        hbox.addStretch(1)
Exemple #4
0
    def Setup(self, featureName, descriptionLabel="Description:"):
        self.featureName = featureName
        self.descriptionLabel = descriptionLabel
        self.parameters = collections.OrderedDict()

        self.descriptionAction = qt.QWidgetAction(self)
        self.descriptionAction.setDefaultWidget(self.descriptionLabel)
        self.closeAction = qt.QAction("Close", self)
        self.reloadActions()
Exemple #5
0
    def addParameter(self, parameterName):
        self.parameters[parameterName] = {}
        self.parameters[parameterName]['Action'] = qt.QAction(
            ('Edit %s' % parameterName), self)
        self.parameters[parameterName][
            'Edit Window'] = FeatureWidgetHelperLib.ParameterEditWindow(
                self, self.featureName, parameterName)

        self.parameters[parameterName]['Action'].connect(
            'triggered()',
            lambda parameterName=parameterName: self.parameters[parameterName][
                'Edit Window'].showWindow())
        self.reloadActions()
def _makeAction(parent, text, icon=None, shortcut=None, slot=None):
  action = qt.QAction(text, parent)

  if icon is not None:
    action.setIcon(qt.QIcon.fromTheme(icon))

  if shortcut is not None:
    action.shortcut = qt.QKeySequence.fromString(shortcut)
    action.shortcutContext = qt.Qt.WidgetWithChildrenShortcut

  if slot is not None:
    action.connect('triggered(bool)', slot)

  parent.addAction(action)

  return action
Exemple #7
0
  def createButtonRow(self, effects, rowLabel=""):
    """ create a row of the edit box given a list of
    effect names (items in _effects(list) """

    rowFrame = qt.QFrame(self.mainFrame)
    self.mainFrame.layout().addWidget(rowFrame)
    self.rowFrames.append(rowFrame)
    rowFrame.objectName = "RowFrame%s" % len(self.rowFrames)
    hbox = qt.QHBoxLayout()
    rowFrame.setLayout( hbox )

    if rowLabel:
      label = qt.QLabel(rowLabel)
      hbox.addWidget(label)

    for effect in effects:
      # check that the effect belongs in our list of effects before including
      if (effect in self.effects):
        i = self.icons[effect] = qt.QIcon(self.effectIconFiles[effect])
        a = self.actions[effect] = qt.QAction(i, '', rowFrame)
        a.objectName = effect + 'Action'
        self.effectButtons[effect] = b = self.buttons[effect] = qt.QToolButton()
        b.objectName = effect + 'ToolButton'
        b.setDefaultAction(a)
        b.setToolTip(effect)
        if EditBox.displayNames.has_key(effect):
          b.setToolTip(EditBox.displayNames[effect])
        hbox.addWidget(b)

        if effect not in ('EraseLabel', 'PreviousCheckPoint', 'NextCheckPoint'):
          # Mapping between action and its associated effect, is done
          # in function'_onEffectActionTriggered' by retrieving the 'effectName'
          # property.
          a.checkable = True
          a.setProperty('effectName', effect)
          self.effectActionGroup.addAction(a)
        elif effect == 'EraseLabel':
          a.checkable = True
          a.connect('triggered(bool)', self._onEraseLabelActionTriggered)
        elif effect == 'PreviousCheckPoint':
          a.connect('triggered(bool)', self.undoRedo.undo)
        elif effect == 'NextCheckPoint':
          a.connect('triggered(bool)', self.undoRedo.redo)

    hbox.addStretch(1)
Exemple #8
0
    def setup(self):

        #
        # servers
        #

        # testing server - not exposed (used for development)
        self.localFrame = ctk.ctkCollapsibleButton(self.parent)
        self.localFrame.setLayout(qt.QVBoxLayout())
        self.localFrame.setText("Servers")
        self.layout.addWidget(self.localFrame)
        self.localFrame.collapsed = False

        self.toggleServer = qt.QPushButton("Start Testing Server")
        self.localFrame.layout().addWidget(self.toggleServer)
        self.toggleServer.connect('clicked()', self.onToggleServer)

        self.verboseServer = qt.QCheckBox("Verbose")
        self.localFrame.layout().addWidget(self.verboseServer)

        # advanced options - not exposed to end users
        # developers can uncomment these lines to access testing server
        self.toggleServer.hide()
        self.verboseServer.hide()

        # Listener

        settings = qt.QSettings()
        self.toggleListener = qt.QPushButton()
        if hasattr(slicer, 'dicomListener'):
            self.toggleListener.text = "Stop Listener"
            slicer.dicomListener.process.connect('stateChanged(int)',
                                                 self.onListenerStateChanged)
        else:
            self.toggleListener.text = "Start Listener"
        self.localFrame.layout().addWidget(self.toggleListener)
        self.toggleListener.connect('clicked()', self.onToggleListener)

        self.runListenerAtStart = qt.QCheckBox(
            "Start Listener when Slicer Starts")
        self.localFrame.layout().addWidget(self.runListenerAtStart)
        if settings.contains('DICOM/RunListenerAtStart'):
            self.runListenerAtStart.checked = bool(
                settings.value('DICOM/RunListenerAtStart') == 'true')
        self.runListenerAtStart.connect('clicked()', self.onRunListenerAtStart)

        # the Database frame (home of the ctkDICOM widget)
        self.dicomFrame = ctk.ctkCollapsibleButton(self.parent)
        self.dicomFrame.setLayout(qt.QVBoxLayout())
        self.dicomFrame.setText("DICOM Database and Networking")
        self.layout.addWidget(self.dicomFrame)

        # initialize the dicomDatabase
        #   - pick a default and let the user know
        if not slicer.dicomDatabase:
            self.promptForDatabaseDirectory()

        #
        # create and configure the app widget - this involves
        # reaching inside and manipulating the widget hierarchy
        # - TODO: this configurability should be exposed more natively
        #   in the CTK code to avoid the findChildren calls
        #
        self.dicomBrowser = ctk.ctkDICOMBrowser()
        DICOM.setDatabasePrecacheTags(self.dicomBrowser)

        self.detailsPopup = DICOMLib.DICOMDetailsPopup(
            self.dicomBrowser,
            setBrowserPersistence=self.setBrowserPersistence)

        self.tables = self.detailsPopup.tables

        self.showBrowser = qt.QPushButton('Show DICOM Browser')
        self.dicomFrame.layout().addWidget(self.showBrowser)
        self.showBrowser.connect('clicked()', self.detailsPopup.open)

        # connect to the main window's dicom button
        mw = slicer.util.mainWindow()
        try:
            action = slicer.util.findChildren(mw, name='actionLoadDICOM')[0]
            action.connect('triggered()', self.detailsPopup.open)
        except IndexError:
            print('Could not connect to the main window DICOM button')

        # connect to our menu file entry so it raises the browser
        fileMenu = slicer.util.lookupTopLevelWidget('FileMenu')
        if fileMenu:
            for action in fileMenu.actions():
                if action.text == 'DICOM':
                    action.connect('triggered()', self.detailsPopup.open)

        # make the tables view a bit bigger
        self.tables.setMinimumHeight(250)

        if hasattr(slicer, 'dicomListener'):
            slicer.dicomListener.fileToBeAddedCallback = self.onListenerToAddFile
            slicer.dicomListener.fileAddedCallback = self.onListenerAddedFile

        # TODO: populate context menu
        self.contextMenu = qt.QMenu(self.tables)
        self.exportAction = qt.QAction("Export to Study", self.contextMenu)
        self.contextMenu.addAction(self.exportAction)
        self.exportAction.enabled = False
        self.deleteAction = qt.QAction("Delete", self.contextMenu)
        self.contextMenu.addAction(self.deleteAction)
        self.deleteAction.enabled = False
        self.contextMenu.connect('triggered(QAction*)',
                                 self.onContextMenuTriggered)

        slicer.dicomDatabase.connect('databaseChanged()',
                                     self.onDatabaseChanged)
        self.dicomBrowser.connect('databaseDirectoryChanged(QString)',
                                  self.onDatabaseDirectoryChanged)
        self.tables.connect('seriesSelectionChanged(QStringList)',
                            self.onSeriesSelected)
        self.tables.setContextMenuPolicy(3)
        self.tables.connect('customContextMenuRequested(QPoint)',
                            self.onTreeContextMenuRequested)

        # enable to the Send button of the app widget and take it over
        # for our purposes - TODO: fix this to enable it at the ctkDICOM level
        self.sendButton = slicer.util.findChildren(self.dicomBrowser,
                                                   text='Send')[0]
        self.sendButton.enabled = False
        self.sendButton.connect('triggered()', self.onSendClicked)

        # the recent activity frame
        self.activityFrame = ctk.ctkCollapsibleButton(self.parent)
        self.activityFrame.setLayout(qt.QVBoxLayout())
        self.activityFrame.setText("Recent DICOM Activity")
        self.layout.addWidget(self.activityFrame)

        self.recentActivity = DICOMLib.DICOMRecentActivityWidget(
            self.activityFrame, detailsPopup=self.detailsPopup)
        self.activityFrame.layout().addWidget(self.recentActivity.widget)
        self.requestUpdateRecentActivity()

        # Add spacer to layout
        self.layout.addStretch(1)
Exemple #9
0
    def setup(self):

        #
        # servers
        #

        # testing server - not exposed (used for development)
        self.localFrame = ctk.ctkCollapsibleButton(self.parent)
        self.localFrame.setLayout(qt.QVBoxLayout())
        self.localFrame.setText("Servers")
        self.layout.addWidget(self.localFrame)
        self.localFrame.collapsed = False

        self.toggleServer = qt.QPushButton("Start Testing Server")
        self.localFrame.layout().addWidget(self.toggleServer)
        self.toggleServer.connect('clicked()', self.onToggleServer)

        self.verboseServer = qt.QCheckBox("Verbose")
        self.localFrame.layout().addWidget(self.verboseServer)

        # advanced options - not exposed to end users
        # developers can uncomment these lines to access testing server
        self.toggleServer.hide()
        self.verboseServer.hide()

        # Listener

        settings = qt.QSettings()
        self.toggleListener = qt.QPushButton()
        if hasattr(slicer, 'dicomListener'):
            self.toggleListener.text = "Stop Listener"
            slicer.dicomListener.process.connect('stateChanged(int)',
                                                 self.onListenerStateChanged)
        else:
            self.toggleListener.text = "Start Listener"
        self.localFrame.layout().addWidget(self.toggleListener)
        self.toggleListener.connect('clicked()', self.onToggleListener)

        self.runListenerAtStart = qt.QCheckBox(
            "Start Listener when Slicer Starts")
        self.localFrame.layout().addWidget(self.runListenerAtStart)
        if settings.contains('DICOM/RunListenerAtStart'):
            self.runListenerAtStart.checked = bool(
                settings.value('DICOM/RunListenerAtStart'))
        self.runListenerAtStart.connect('clicked()', self.onRunListenerAtStart)

        # the Database frame (home of the ctkDICOM widget)
        self.dicomFrame = ctk.ctkCollapsibleButton(self.parent)
        self.dicomFrame.setLayout(qt.QVBoxLayout())
        self.dicomFrame.setText("DICOM Database and Networking")
        self.layout.addWidget(self.dicomFrame)

        # initialize the dicomDatabase
        # - don't let the user escape without
        #   picking a valid database directory
        while not slicer.dicomDatabase:
            self.promptForDatabaseDirectory()

        #
        # create and configure the app widget - this involves
        # reaching inside and manipulating the widget hierarchy
        # - TODO: this configurability should be exposed more natively
        #   in the CTK code to avoid the findChildren calls
        #
        self.dicomApp = ctk.ctkDICOMAppWidget()
        if self.hideSearch:
            # hide the search options - doesn't work yet and doesn't fit
            # well into the frame
            slicer.util.findChildren(self.dicomApp, 'SearchOption')[0].hide()

        self.detailsPopup = DICOMLib.DICOMDetailsPopup(
            self.dicomApp, setBrowserPersistence=self.setBrowserPersistence)

        self.tree = self.detailsPopup.tree

        self.showBrowser = qt.QPushButton('Show DICOM Browser')
        self.dicomFrame.layout().addWidget(self.showBrowser)
        self.showBrowser.connect('clicked()', self.detailsPopup.open)

        # make the tree view a bit bigger
        self.tree.setMinimumHeight(250)

        if hasattr(slicer, 'dicomListener'):
            slicer.dicomListener.fileToBeAddedCallback = self.onListenerToAddFile
            slicer.dicomListener.fileAddedCallback = self.onListenerAddedFile

        self.contextMenu = qt.QMenu(self.tree)
        self.exportAction = qt.QAction("Export to Study", self.contextMenu)
        self.contextMenu.addAction(self.exportAction)
        self.exportAction.enabled = False
        self.deleteAction = qt.QAction("Delete", self.contextMenu)
        self.contextMenu.addAction(self.deleteAction)
        self.contextMenu.connect('triggered(QAction*)',
                                 self.onContextMenuTriggered)

        slicer.dicomDatabase.connect('databaseChanged()',
                                     self.onDatabaseChanged)
        self.dicomApp.connect('databaseDirectoryChanged(QString)',
                              self.onDatabaseDirectoryChanged)
        selectionModel = self.tree.selectionModel()
        # TODO: can't use this because QList<QModelIndex> is not visible in PythonQt
        #selectionModel.connect('selectionChanged(QItemSelection, QItemSelection)', self.onTreeSelectionChanged)
        self.tree.connect('clicked(QModelIndex)', self.onTreeClicked)
        self.tree.setContextMenuPolicy(3)
        self.tree.connect('customContextMenuRequested(QPoint)',
                          self.onTreeContextMenuRequested)

        # enable to the Send button of the app widget and take it over
        # for our purposes - TODO: fix this to enable it at the ctkDICOM level
        self.sendButton = slicer.util.findChildren(self.dicomApp,
                                                   text='Send')[0]
        self.sendButton.enabled = False
        self.sendButton.connect('clicked()', self.onSendClicked)

        # the recent activity frame
        self.activityFrame = ctk.ctkCollapsibleButton(self.parent)
        self.activityFrame.setLayout(qt.QVBoxLayout())
        self.activityFrame.setText("Recent DICOM Activity")
        self.layout.addWidget(self.activityFrame)

        self.recentActivity = DICOMLib.DICOMRecentActivityWidget(
            self.activityFrame, detailsPopup=self.detailsPopup)
        self.activityFrame.layout().addWidget(self.recentActivity.widget)
        self.requestUpdateRecentActivity()

        # Add spacer to layout
        self.layout.addStretch(1)
Exemple #10
0
    def setup(self):

        #
        # servers
        #

        # testing server - not exposed (used for development)
        self.localFrame = ctk.ctkCollapsibleButton(self.parent)
        self.localFrame.setLayout(qt.QVBoxLayout())
        self.localFrame.setText("Servers")
        self.layout.addWidget(self.localFrame)
        self.localFrame.collapsed = False

        self.toggleServer = qt.QPushButton("Start Testing Server")
        self.localFrame.layout().addWidget(self.toggleServer)
        self.toggleServer.connect('clicked()', self.onToggleServer)

        self.verboseServer = qt.QCheckBox("Verbose")
        self.localFrame.layout().addWidget(self.verboseServer)

        # advanced options - not exposed to end users
        # developers can uncomment these lines to access testing server
        self.toggleServer.hide()
        self.verboseServer.hide()

        # Listener

        settings = qt.QSettings()
        self.toggleListener = qt.QPushButton()
        if hasattr(slicer, 'dicomListener'):
            self.toggleListener.text = "Stop Listener"
            slicer.dicomListener.process.connect('stateChanged(int)',
                                                 self.onListenerStateChanged)
        else:
            self.toggleListener.text = "Start Listener"
        self.localFrame.layout().addWidget(self.toggleListener)
        self.toggleListener.connect('clicked()', self.onToggleListener)

        self.runListenerAtStart = qt.QCheckBox(
            "Start Listener when Slicer Starts")
        self.localFrame.layout().addWidget(self.runListenerAtStart)
        if settings.contains('DICOM/RunListenerAtStart'):
            self.runListenerAtStart.checked = bool(
                settings.value('DICOM/RunListenerAtStart'))
        self.runListenerAtStart.connect('clicked()', self.onRunListenerAtStart)

        # the Database frame (home of the ctkDICOM widget)
        self.dicomFrame = ctk.ctkCollapsibleButton(self.parent)
        self.dicomFrame.setLayout(qt.QVBoxLayout())
        self.dicomFrame.setText("DICOM Database and Networking")
        self.layout.addWidget(self.dicomFrame)

        self.dicomApp = ctk.ctkDICOMAppWidget()
        self.dicomFrame.layout().addWidget(self.dicomApp)
        # hide the search options - doesn't work yet and doesn't fit
        # well into the frame
        self.findChildren(self.dicomApp, 'SearchOption')[0].hide()
        # make the tree a little smaller to fit in slicer
        self.tree = self.findChildren(self.dicomApp, 'TreeView')[0]
        g = self.tree.geometry
        g.setHeight(150)
        if self.dicomApp.databaseDirectory:
            self.onDatabaseDirectoryChanged(self.dicomApp.databaseDirectory)
        else:
            self.promptForDatabaseDirectory()
        if hasattr(slicer, 'dicomListener'):
            slicer.dicomListener.fileAddedCallback = self.onListenerAddedFile

        self.contextMenu = qt.QMenu(self.tree)
        self.deleteAction = qt.QAction("Delete", self.contextMenu)
        self.contextMenu.addAction(self.deleteAction)
        self.contextMenu.connect('triggered(QAction*)',
                                 self.onContextMenuTriggered)

        self.dicomApp.connect('databaseDirectoryChanged(QString)',
                              self.onDatabaseDirectoryChanged)
        self.tree.connect('clicked(const QModelIndex&)', self.onTreeClicked)
        self.tree.setContextMenuPolicy(3)
        self.tree.connect('customContextMenuRequested(QPoint)',
                          self.onTreeContextMenuRequested)

        userFrame = self.findChildren(self.dicomApp, 'UserFrame')[0]
        userFrame.setLayout(qt.QVBoxLayout())
        self.treeLabel = qt.QLabel('Selection: None')
        userFrame.layout().addWidget(self.treeLabel)
        self.loadButton = qt.QPushButton('Load to Slicer')
        self.loadButton.enabled = False
        userFrame.layout().addWidget(self.loadButton)
        self.loadButton.connect('clicked()', self.onLoadButton)
        self.exportButton = qt.QPushButton('Export Slicer Data to Study...')
        self.exportButton.enabled = False
        userFrame.layout().addWidget(self.exportButton)
        self.exportButton.connect('clicked()', self.onExportClicked)

        # enable to the Send button of the app widget and take it over
        # for our purposes - TODO: fix this to enable it at the ctkDICOM level
        self.sendButton = slicer.util.findChildren(self.dicomApp,
                                                   text='Send')[0]
        self.sendButton.enabled = False
        self.sendButton.connect('clicked()', self.onSendClicked)

        # Add spacer to layout
        self.layout.addStretch(1)
Exemple #11
0
    def create(self):
        """create the segmentation helper box"""

        #
        # Master Frame
        #
        self.masterFrame = qt.QFrame(self.parent)
        self.masterFrame.setLayout(qt.QVBoxLayout())
        self.parent.layout().addWidget(self.masterFrame)

        #
        # the master volume selector
        #
        self.masterSelectorFrame = qt.QFrame(self.parent)
        self.masterSelectorFrame.objectName = 'MasterVolumeFrame'
        self.masterSelectorFrame.setLayout(qt.QHBoxLayout())
        self.masterFrame.layout().addWidget(self.masterSelectorFrame)

        self.masterSelectorLabel = qt.QLabel("Master Volume: ",
                                             self.masterSelectorFrame)
        self.masterSelectorLabel.setToolTip(
            "Select the master volume (background grayscale scalar volume node)"
        )
        self.masterSelectorFrame.layout().addWidget(self.masterSelectorLabel)

        self.masterSelector = slicer.qMRMLNodeComboBox(
            self.masterSelectorFrame)
        self.masterSelector.objectName = 'MasterVolumeNodeSelector'
        # TODO
        self.masterSelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
        self.masterSelector.selectNodeUponCreation = False
        self.masterSelector.addEnabled = False
        self.masterSelector.removeEnabled = False
        self.masterSelector.noneEnabled = True
        self.masterSelector.showHidden = False
        self.masterSelector.showChildNodeTypes = False
        self.masterSelector.setMRMLScene(slicer.mrmlScene)
        # TODO: need to add a QLabel
        # self.masterSelector.SetLabelText( "Master Volume:" )
        self.masterSelector.setToolTip(
            "Pick the master structural volume to define the segmentation.  A label volume with the with \"%s\" appended to the name will be created if it doesn't already exist."
            % self.mergeVolumePostfix)
        self.masterSelectorFrame.layout().addWidget(self.masterSelector)

        #
        # merge label name and set button
        #
        self.mergeSelectorFrame = qt.QFrame(self.masterFrame)
        self.mergeSelectorFrame.objectName = 'MergeVolumeFrame'
        self.mergeSelectorFrame.setLayout(qt.QHBoxLayout())
        self.masterFrame.layout().addWidget(self.mergeSelectorFrame)

        mergeNameToolTip = "Composite label map containing the merged structures (be aware that merge operations will overwrite any edits applied to this volume)"
        self.mergeNameLabel = qt.QLabel("Merge Volume: ",
                                        self.mergeSelectorFrame)
        self.mergeNameLabel.setToolTip(mergeNameToolTip)
        self.mergeSelectorFrame.layout().addWidget(self.mergeNameLabel)

        self.mergeSelector = slicer.qMRMLNodeComboBox(self.mergeSelectorFrame)
        self.mergeSelector.objectName = 'MergeVolumeNodeSelector'
        self.mergeSelector.setToolTip(mergeNameToolTip)
        self.mergeSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode"]
        self.mergeSelector.addEnabled = False
        self.mergeSelector.removeEnabled = False
        self.mergeSelector.noneEnabled = False
        self.mergeSelector.showHidden = False
        self.mergeSelector.showChildNodeTypes = False
        self.mergeSelector.setMRMLScene(slicer.mrmlScene)
        self.mergeSelectorFrame.layout().addWidget(self.mergeSelector)

        self.newMergeVolumeAction = qt.QAction("Create new LabelMapVolume",
                                               self.mergeSelector)
        self.newMergeVolumeAction.connect("triggered()", self.newMerge)
        self.mergeSelector.addMenuAction(self.newMergeVolumeAction)

        #
        # Structures Frame
        #
        self.structuresFrame = ctk.ctkCollapsibleGroupBox(self.masterFrame)
        self.structuresFrame.objectName = 'PerStructureVolumesFrame'
        self.structuresFrame.title = "Per-Structure Volumes"
        self.structuresFrame.collapsed = True
        self.structuresFrame.setLayout(qt.QVBoxLayout())
        self.masterFrame.layout().addWidget(self.structuresFrame)

        self.structureListWidget = LabelStructureListWidget()
        self.structureListWidget.mergeVolumePostfix = self.mergeVolumePostfix
        self.structuresFrame.layout().addWidget(self.structureListWidget)

        #
        # signals, slots, and observers
        #

        # signals/slots on qt widgets are automatically when
        # this class destructs, but observers of the scene must be explicitly
        # removed in the destuctor

        # node selected
        self.masterSelector.connect("currentNodeChanged(vtkMRMLNode*)",
                                    self.onSelect)
        self.mergeSelector.connect("currentNodeChanged(vtkMRMLNode*)",
                                   self.onMergeSelect)

        # so buttons will initially be disabled
        self.master = None
        self.structureListWidget.updateStructures()