def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg() self.featureDlg.setWindowTitle("Features") # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append( FeatureEntry(featureName) ) groupedNames.append( (group, featureEntries) ) self.featureDlg.createFeatureTable( groupedNames, self.ScalesList ) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.mainOperator.FeatureIds.value) cols = len(self.mainOperator.Scales.value) defaultFeatures = numpy.zeros((rows,cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg)
def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, self.ScalesList) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) # Disable the first column, except for the first item. # This is a slightly hacky way of fixing ilastik issue #610. # Besides color, the features at a sigma of 0.3 are not valid because the # results are overwhelmed by the inherent sampling noise of the filter. # (This is a bit hacky because we ASSUME the first feature is Gaussian # Smoothing. It works for now.) enabled_item_mask = numpy.ones(defaultFeatures.shape, dtype=bool) enabled_item_mask[1:, 0] = False # hacky self.featureDlg.setEnableItemMask(enabled_item_mask)
def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) self.featureDlg.setImageToPreView(None) self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg)
def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, self.ScalesList) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg)
class FeatureSelectionGui(LayerViewerGui): """ """ # Constants ScalesList = [0.3, 0.7, 1, 1.6, 3.5, 5.0, 10.0] # Map feature groups to lists of feature IDs FeatureGroups = [ ("Color/Intensity", ["GaussianSmoothing"]), ("Edge", ["LaplacianOfGaussian", "GaussianGradientMagnitude", "DifferenceOfGaussians"]), ("Texture", ["StructureTensorEigenvalues", "HessianOfGaussianEigenvalues"]), ] # Map feature IDs to feature names FeatureNames = { "GaussianSmoothing": "Gaussian Smoothing", "LaplacianOfGaussian": "Laplacian of Gaussian", "GaussianGradientMagnitude": "Gaussian Gradient Magnitude", "DifferenceOfGaussians": "Difference of Gaussians", "StructureTensorEigenvalues": "Structure Tensor EigenValues", "HessianOfGaussianEigenvalues": "Hessian of Gaussian Eigenvalues", } ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() self.drawer.caption.setText("(No features selected)") # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() # Why is this necessary? # Clearing the layerstack doesn't seem to call the rowsRemoved signal? self._viewerControlWidget.featureListWidget.clear() # (Other methods already provided by our base class) ########################################### ########################################### def __init__(self, parentApplet, topLevelOperatorView): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(parentApplet, topLevelOperatorView, crosshair=False) self.parentApplet = parentApplet self.__cleanup_fns = [] self.topLevelOperatorView.SelectionMatrix.notifyDirty(bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.FeatureListFilename.notifyDirty(bind(self.onFeaturesSelectionsChanged)) self.__cleanup_fns.append( partial(self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged)) ) self.__cleanup_fns.append( partial( self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged) ) ) self.onFeaturesSelectionsChanged() # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in self.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.topLevelOperatorView.Scales.setValue(self.ScalesList) self.topLevelOperatorView.FeatureIds.setValue(self.getFeatureIdOrder()) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect(self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect(self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi(os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = i == row def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(range(start, end + 1)): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect(handleRemovedLayers) self.layerstack.rowsInserted.connect(handleInsertedLayers) layerListWidget.currentRowChanged.connect(handleSelectionChanged) def setupLayers(self): opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "Raw Data (display only)" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels / numInputChannels assert ( 0 < featureChannelsPerInputChannel <= 3 ), "The feature selection Gui does not yet support features with more than three channels per input channel." for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ["R", "G", "B"] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Start.setValue(tuple(start)) opSubRegion.Stop.setValue(tuple(stop)) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, self.ScalesList) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) # Disable the first column, except for the first item. # This is a slightly hacky way of fixing ilastik issue #610. # Besides color, the features at a sigma of 0.3 are not valid because the # results are overwhelmed by the inherent sampling noise of the filter. # (This is a bit hacky because we ASSUME the first feature is Gaussian # Smoothing. It works for now.) enabled_item_mask = numpy.ones(defaultFeatures.shape, dtype=bool) enabled_item_mask[1:, 0] = False # hacky self.featureDlg.setEnableItemMask(enabled_item_mask) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filename = QFileDialog.getOpenFileName(self, "Open Feature List", ".", options=options) filename = encode_from_qstring(filename) # sanity checks on the given file if not filename: return if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return f = open(filename, "r") with f: for line in f: line = line.strip() if len(line) == 0: continue if not os.path.exists(line): QMessageBox.critical( self, "Open Feature List", "File '%s', referenced in '%s', does not exist" % (line, filename) ) return try: h = h5py.File(line, "r") with h: assert len(h["data"].shape) == 3 except: QMessageBox.critical( self, "Open Feature List", "File '%s', referenced in '%s', could not be opened as an HDF5 file or does not contain a 3D dataset called 'data'" % (line, filename), ) return self.topLevelOperatorView.FeatureListFilename.setValue(filename) self.topLevelOperatorView._setupOutputs() self.onFeaturesSelectionsChanged() # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested.emit() def onFeatureButtonClicked(self): self.topLevelOperatorView.FeatureListFilename.setValue("") # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.topLevelOperatorView.SelectionMatrix.ready() and self.topLevelOperatorView.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.topLevelOperatorView.SelectionMatrix.value featureOrdering = self.topLevelOperatorView.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in self.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray(self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): # Disable gui self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() QApplication.instance().setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.instance().processEvents() try: opFeatureSelection.SelectionMatrix.setValue(featureMatrix) except DatasetConstraintError as ex: # The user selected some scales that were too big. QMessageBox.critical(self, "Invalid selections", ex.message) opFeatureSelection.SelectionMatrix.disconnect() # Re-enable gui QApplication.instance().restoreOverrideCursor() self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested.emit() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption fff = ( self.topLevelOperatorView.FeatureListFilename.ready() and len(self.topLevelOperatorView.FeatureListFilename.value) != 0 ) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText("(No features selected)") self.layerstack.clear() elif fff: self.drawer.caption.setText("(features from files)") else: self.initFeatureOrder() matrix = self.topLevelOperatorView.SelectionMatrix.value self.drawer.caption.setText("(Selected %d features)" % numpy.sum(matrix))
class FeatureSelectionGui(LayerViewerGui): """ """ # ########################################## # ## AppletGuiInterface Concrete Methods ### # ########################################## def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() def __init__(self, parentApplet, topLevelOperatorView): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(parentApplet, topLevelOperatorView, crosshair=False) self.parentApplet = parentApplet self.__cleanup_fns = [] self.topLevelOperatorView.InputImage.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.FeatureListFilename.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.__cleanup_fns.append( partial(self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.__cleanup_fns.append( partial( self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) # Init feature dialog self.initFeatureDlg() self.onFeaturesSelectionsChanged() def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect( self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect( self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi( os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = i == row def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(list(range(start, end + 1))): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect(handleRemovedLayers) self.layerstack.rowsInserted.connect(handleInsertedLayers) layerListWidget.currentRowChanged.connect(handleSelectionChanged) # Support the same right-click menu as 'normal' layer list widgets def showLayerContextMenu(pos): idx = layerListWidget.indexAt(pos) layer = self.layerstack[idx.row()] layercontextmenu(layer, layerListWidget.mapToGlobal(pos), layerListWidget) layerListWidget.customContextMenuRequested.connect( showLayerContextMenu) layerListWidget.setContextMenuPolicy(Qt.CustomContextMenu) def setupLayers(self): if hasattr(self.drawer, "feature2dBox" ): # drawer has to be initialized (initAppletDrawerUi) # set hidden status of feature2dBox again (presence of z axis may have changed) if "z" in self.topLevelOperatorView.InputImage.meta.original_axistags: self.drawer.feature2dBox.setHidden(False) else: self.drawer.feature2dBox.setHidden(True) opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "Raw Data (display only)" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels // numInputChannels if not 0 < featureChannelsPerInputChannel <= 3: logger.warning( "The feature selection Gui does not yet support features with more than three channels per " "input channel. Some features will not be displayed entirely.") for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ["R", "G", "B"] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Roi.setValue((tuple(start), tuple(stop))) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = preferences.get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) preferences.set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) self.featureDlg.setImageToPreView(None) self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filenames, _filter = QFileDialog.getOpenFileNames(self, "Open Feature Files", ".", options=options) # Check if file exists if not filenames: return for filename in filenames: if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return num_lanes = len(self.parentApplet.topLevelOperator.FeatureListFilename) if num_lanes != len(filenames): QMessageBox.critical( self, "Wrong number of feature files", "You must select all pre-computed feature files at once (shift-click).\n" "You selected {} file(s), but there are {} image(s) loaded". format(len(filenames), num_lanes), ) return for filename, slot in zip( filenames, self.parentApplet.topLevelOperator.FeatureListFilename): slot.setValue(filename) # Create a dummy SelectionMatrix, just so the operator knows it is configured # This is a little hacky. We should really make SelectionMatrix optional, # and then handle the choice correctly in setupOutputs, probably involving # the Output.meta.NOTREADY flag dummy_matrix = numpy.zeros((6, 7), dtype=bool) dummy_matrix[0, 0] = True self.parentApplet.topLevelOperator.SelectionMatrix.setValue(True) # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeatureButtonClicked(self): # Remove all pre-computed feature files for slot in self.parentApplet.topLevelOperator.FeatureListFilename: slot.disconnect() # The first time we open feature selection, the minimal set of features should be set. Afterwards, if the # data input is changed, the feature selection dialog should appear, if adjustments are necessary if not self.topLevelOperatorView.SelectionMatrix.ready(): self.topLevelOperatorView.SelectionMatrix.setValue( self.topLevelOperatorView.MinimalFeatures) # Other slots need to be ready (they also should, as they have default values) assert self.topLevelOperatorView.FeatureIds.ready() assert self.topLevelOperatorView.Scales.ready() assert self.topLevelOperatorView.ComputeIn2d.ready( ), self.topLevelOperatorView.ComputeIn2d.value # Refresh the dialog data in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) # This also ensures to restore the selection after previously canceling the feature dialog opFeatureSelection = self.topLevelOperatorView # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in opFeatureSelection.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = opFeatureSelection.FeatureNames[featureId] availableFilterOps = { key[2:]: value for key, value in filterOps.__dict__.items() if key.startswith("Op") } minimum_scale = availableFilterOps[featureId].minimum_scale featureEntries.append(FeatureEntry(featureName, minimum_scale)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable( groupedNames, opFeatureSelection.Scales.value, opFeatureSelection.ComputeIn2d.value, opFeatureSelection.WINDOW_SIZE, ) # update feature dialog to show/hide z dimension specific 'compute in 2d' flags if self.topLevelOperatorView.InputImage.ready( ) and self.topLevelOperatorView.ComputeIn2d.value: ts = self.topLevelOperatorView.InputImage.meta.getTaggedShape() hide = ("z" not in ts or ts["z"] == 1) and all( self.topLevelOperatorView.ComputeIn2d.value) self.featureDlg.setComputeIn2dHidden(hide) matrix = opFeatureSelection.SelectionMatrix.value featureOrdering = opFeatureSelection.FeatureIds.value # Re-order the feature matrix using the loaded feature ids reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in OpFeatureSelection.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectionMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView if opFeatureSelection is not None: # Save previous settings old_scales = opFeatureSelection.Scales.value old_computeIn2d = opFeatureSelection.ComputeIn2d.value old_features = opFeatureSelection.SelectionMatrix.value # Disable gui self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested() QApplication.instance().setOverrideCursor(QCursor(Qt.WaitCursor)) try: # Apply new settings # Disconnect an input (used like a transaction slot) opFeatureSelection.SelectionMatrix.disconnect() opFeatureSelection.Scales.setValue(self.featureDlg.scales) opFeatureSelection.ComputeIn2d.setValue( self.featureDlg.computeIn2d) # set disconnected slot at last (used like a transaction slot) opFeatureSelection.SelectionMatrix.setValue( self.featureDlg.selectionMatrix) except (DatasetConstraintError, RuntimeError) as ex: # The user selected some scales that were too big. if isinstance(ex, DatasetConstraintError): QMessageBox.critical(self, "Invalid selection", ex.message) else: QMessageBox.critical( self, "Invalid selection", "You selected the exact same feature twice.") # Restore previous settings opFeatureSelection.SelectionMatrix.disconnect() opFeatureSelection.Scales.setValue(old_scales) opFeatureSelection.ComputeIn2d.setValue(old_computeIn2d) opFeatureSelection.SelectionMatrix.setValue(old_features) # Re-enable gui QApplication.instance().restoreOverrideCursor() self.parentApplet.busy = False # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's ImageInput and matrix of feature selections. """ # Update the drawer caption fff = (self.topLevelOperatorView.FeatureListFilename.ready() and len(self.topLevelOperatorView.FeatureListFilename.value) != 0) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText("(No features selected)") self.layerstack.clear() elif fff: self.drawer.caption.setText("(features from files)") else: nr_feat = self.topLevelOperatorView.SelectionMatrix.value.sum() self.drawer.caption.setText(f"(Selected {nr_feat} features)")
class FeatureSelectionGui(LayerViewerGui): """ """ # ########################################## # ## AppletGuiInterface Concrete Methods ### # ########################################## def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() def __init__(self, parentApplet, topLevelOperatorView): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(parentApplet, topLevelOperatorView, crosshair=False) self.parentApplet = parentApplet self.__cleanup_fns = [] self.topLevelOperatorView.InputImage.notifyDirty(bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.SelectionMatrix.notifyDirty(bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.FeatureListFilename.notifyDirty(bind(self.onFeaturesSelectionsChanged)) self.__cleanup_fns.append(partial(self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.__cleanup_fns.append(partial(self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) # Init feature dialog self.initFeatureDlg() self.onFeaturesSelectionsChanged() def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect(self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect(self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi(os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(list(range(start, end + 1))): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect(handleRemovedLayers) self.layerstack.rowsInserted.connect(handleInsertedLayers) layerListWidget.currentRowChanged.connect(handleSelectionChanged) # Support the same right-click menu as 'normal' layer list widgets def showLayerContextMenu(pos): idx = layerListWidget.indexAt(pos) layer = self.layerstack[idx.row()] layercontextmenu(layer, layerListWidget.mapToGlobal(pos), layerListWidget) layerListWidget.customContextMenuRequested.connect(showLayerContextMenu) layerListWidget.setContextMenuPolicy(Qt.CustomContextMenu) def setupLayers(self): if hasattr(self.drawer, 'feature2dBox'): # drawer has to be initialized (initAppletDrawerUi) # set hidden status of feature2dBox again (presence of z axis may have changed) if 'z' in self.topLevelOperatorView.InputImage.meta.original_axistags: self.drawer.feature2dBox.setHidden(False) else: self.drawer.feature2dBox.setHidden(True) opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "Raw Data (display only)" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels // numInputChannels if not 0 < featureChannelsPerInputChannel <= 3: logger.warning('The feature selection Gui does not yet support features with more than three channels per ' 'input channel. Some features will not be displayed entirely.') for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Roi.setValue((tuple(start), tuple(stop))) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) self.featureDlg.setImageToPreView(None) self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filenames, _filter = QFileDialog.getOpenFileNames(self, 'Open Feature Files', '.', options=options) # Check if file exists if not filenames: return for filename in filenames: if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return num_lanes = len(self.parentApplet.topLevelOperator.FeatureListFilename) if num_lanes != len(filenames): QMessageBox.critical(self, "Wrong number of feature files", "You must select all pre-computed feature files at once (shift-click).\n" "You selected {} file(s), but there are {} image(s) loaded" .format(len(filenames), num_lanes)) return for filename, slot in zip(filenames, self.parentApplet.topLevelOperator.FeatureListFilename): slot.setValue(filename) # Create a dummy SelectionMatrix, just so the operator knows it is configured # This is a little hacky. We should really make SelectionMatrix optional, # and then handle the choice correctly in setupOutputs, probably involving # the Output.meta.NOTREADY flag dummy_matrix = numpy.zeros((6, 7), dtype=bool) dummy_matrix[0, 0] = True self.parentApplet.topLevelOperator.SelectionMatrix.setValue(True) # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeatureButtonClicked(self): # Remove all pre-computed feature files for slot in self.parentApplet.topLevelOperator.FeatureListFilename: slot.disconnect() # The first time we open feature selection, the minimal set of features should be set. Afterwards, if the # data input is changed, the feature selection dialog should appear, if adjustments are necessary if not self.topLevelOperatorView.SelectionMatrix.ready(): self.topLevelOperatorView.SelectionMatrix.setValue(self.topLevelOperatorView.MinimalFeatures) # Other slots need to be ready (they also should, as they have default values) assert self.topLevelOperatorView.FeatureIds.ready() assert self.topLevelOperatorView.Scales.ready() assert self.topLevelOperatorView.ComputeIn2d.ready(), self.topLevelOperatorView.ComputeIn2d.value # Refresh the dialog data in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) # This also ensures to restore the selection after previously canceling the feature dialog opFeatureSelection = self.topLevelOperatorView # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in opFeatureSelection.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = opFeatureSelection.FeatureNames[featureId] availableFilterOps = {key[2:]: value for key, value in filterOps.__dict__.items() if key.startswith('Op')} minimum_scale = availableFilterOps[featureId].minimum_scale featureEntries.append(FeatureEntry(featureName, minimum_scale)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, opFeatureSelection.Scales.value, opFeatureSelection.ComputeIn2d.value, opFeatureSelection.WINDOW_SIZE) # update feature dialog to show/hide z dimension specific 'compute in 2d' flags if self.topLevelOperatorView.InputImage.ready() and self.topLevelOperatorView.ComputeIn2d.value: ts = self.topLevelOperatorView.InputImage.meta.getTaggedShape() hide = ('z' not in ts or ts['z'] == 1) and all(self.topLevelOperatorView.ComputeIn2d.value) self.featureDlg.setComputeIn2dHidden(hide) matrix = opFeatureSelection.SelectionMatrix.value featureOrdering = opFeatureSelection.FeatureIds.value # Re-order the feature matrix using the loaded feature ids reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in OpFeatureSelection.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectionMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView if opFeatureSelection is not None: # Save previous settings old_scales = opFeatureSelection.Scales.value old_computeIn2d = opFeatureSelection.ComputeIn2d.value old_features = opFeatureSelection.SelectionMatrix.value # Disable gui self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested() QApplication.instance().setOverrideCursor(QCursor(Qt.WaitCursor)) try: # Apply new settings # Disconnect an input (used like a transaction slot) opFeatureSelection.SelectionMatrix.disconnect() opFeatureSelection.Scales.setValue(self.featureDlg.scales) opFeatureSelection.ComputeIn2d.setValue(self.featureDlg.computeIn2d) # set disconnected slot at last (used like a transaction slot) opFeatureSelection.SelectionMatrix.setValue(self.featureDlg.selectionMatrix) except (DatasetConstraintError, RuntimeError) as ex: # The user selected some scales that were too big. if isinstance(ex, DatasetConstraintError): QMessageBox.critical(self, 'Invalid selection', ex.message) else: QMessageBox.critical(self, 'Invalid selection', 'You selected the exact same feature twice.') # Restore previous settings opFeatureSelection.SelectionMatrix.disconnect() opFeatureSelection.Scales.setValue(old_scales) opFeatureSelection.ComputeIn2d.setValue(old_computeIn2d) opFeatureSelection.SelectionMatrix.setValue(old_features) # Re-enable gui QApplication.instance().restoreOverrideCursor() self.parentApplet.busy = False # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's ImageInput and matrix of feature selections. """ # Update the drawer caption fff = (self.topLevelOperatorView.FeatureListFilename.ready() and len(self.topLevelOperatorView.FeatureListFilename.value) != 0) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText("(No features selected)") self.layerstack.clear() elif fff: self.drawer.caption.setText("(features from files)") else: nr_feat = self.topLevelOperatorView.SelectionMatrix.value.sum() self.drawer.caption.setText(f'(Selected {nr_feat} features)')
class FeatureSelectionGui(LayerViewerGui): """ """ # Constants ScalesList = [0.3, 0.7, 1, 1.6, 3.5, 5.0, 10.0] DefaultColorTable = None # # Default order # FeatureIds = [ 'GaussianSmoothing', # 'LaplacianOfGaussian', # 'GaussianGradientMagnitude', # 'DifferenceOfGaussians', # 'StructureTensorEigenvalues', # 'HessianOfGaussianEigenvalues' ] # Map feature groups to lists of feature IDs FeatureGroups = [ ( "Color/Intensity", [ "GaussianSmoothing" ] ), ( "Edge", [ "LaplacianOfGaussian", "GaussianGradientMagnitude", "DifferenceOfGaussians" ] ), ( "Texture", [ "StructureTensorEigenvalues", "HessianOfGaussianEigenvalues" ] ) ] # Map feature IDs to feature names FeatureNames = { 'GaussianSmoothing' : 'Gaussian Smoothing', 'LaplacianOfGaussian' : "Laplacian of Gaussian", 'GaussianGradientMagnitude' : "Gaussian Gradient Magnitude", 'DifferenceOfGaussians' : "Difference of Gaussians", 'StructureTensorEigenvalues' : "Structure Tensor EigenValues", 'HessianOfGaussianEigenvalues' : "Hessian of Gaussian Eigenvalues" } ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawers(self): return [ ("Feature Selection", self.drawer ) ] def viewerControlWidget(self): return self._viewerControlWidget def reset(self): super(FeatureSelectionGui, self).reset() self.drawer.caption.setText( "(No features selected)" ) # Why is this necessary? # Clearing the layerstack doesn't seem to call the rowsRemoved signal? self._viewerControlWidget.listWidget.clear() # (Other methods already provided by our base class) ########################################### ########################################### @traceLogged(traceLogger) def __init__(self, mainOperator): """ """ super(FeatureSelectionGui, self).__init__(mainOperator) self.mainOperator = mainOperator self.mainOperator.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged) ) # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in self.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.mainOperator.Scales.setValue( self.ScalesList ) self.mainOperator.FeatureIds.setValue( self.getFeatureIdOrder() ) @traceLogged(traceLogger) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir+"/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect(self.onFeatureButtonClicked) @traceLogged(traceLogger) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi(os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.listWidget # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) def handleInsertedLayers(parent, start, end): for i in range(start, end+1): layerListWidget.insertItem(i, self.layerstack[i].name) self.layerstack.rowsInserted.connect( handleInsertedLayers ) def handleRemovedLayers(parent, start, end): for i in reversed(range(start, end+1)): layerListWidget.takeItem(i) self.layerstack.rowsRemoved.connect( handleRemovedLayers ) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) layerListWidget.currentRowChanged.connect( handleSelectionChanged ) @traceLogged(traceLogger) def setupLayers(self, currentImageIndex): layers = [] opFeatureSelection = self.operatorForCurrentImage() inputSlot = opFeatureSelection.InputImage featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers @traceLogged(traceLogger) def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels / numInputChannels assert 0 < featureChannelsPerInputChannel <= 3, "The feature selection Gui does not yet support features with more than three channels per input channel." for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(graph=self.mainOperator.graph) opSubRegion.Input.connect( featureSlot ) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel+1) * featureChannelsPerInputChannel opSubRegion.Start.setValue( tuple(start) ) opSubRegion.Stop.setValue( tuple(stop) ) featureLayer = self.createStandardLayerFromSlot( opSubRegion.Output ) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers @traceLogged(traceLogger) def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg() self.featureDlg.setWindowTitle("Features") # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append( FeatureEntry(featureName) ) groupedNames.append( (group, featureEntries) ) self.featureDlg.createFeatureTable( groupedNames, self.ScalesList ) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.mainOperator.FeatureIds.value) cols = len(self.mainOperator.Scales.value) defaultFeatures = numpy.zeros((rows,cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) def onFeatureButtonClicked(self): # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.mainOperator.SelectionMatrix.ready() and self.mainOperator.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.mainOperator.SelectionMatrix.value featureOrdering = self.mainOperator.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in self.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.operatorForCurrentImage() if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray(self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): opFeatureSelection.SelectionMatrix.setValue( featureMatrix ) else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption if not self.mainOperator.SelectionMatrix.ready(): self.drawer.caption.setText( "(No features selected)" ) self.layerstack.clear() else: self.initFeatureOrder() matrix = self.mainOperator.SelectionMatrix.value self.drawer.caption.setText( "(Selected %d features)" % numpy.sum(matrix) )
class FeatureSelectionGui(LayerViewerGui): """ """ # Constants ScalesList = [0.3, 0.7, 1, 1.6, 3.5, 5.0, 10.0] # Map feature groups to lists of feature IDs FeatureGroups = [ ("Color/Intensity", ["GaussianSmoothing"]), ("Edge", [ "LaplacianOfGaussian", "GaussianGradientMagnitude", "DifferenceOfGaussians" ]), ("Texture", ["StructureTensorEigenvalues", "HessianOfGaussianEigenvalues"]) ] # Map feature IDs to feature names FeatureNames = { 'GaussianSmoothing': 'Gaussian Smoothing', 'LaplacianOfGaussian': "Laplacian of Gaussian", 'GaussianGradientMagnitude': "Gaussian Gradient Magnitude", 'DifferenceOfGaussians': "Difference of Gaussians", 'StructureTensorEigenvalues': "Structure Tensor EigenValues", 'HessianOfGaussianEigenvalues': "Hessian of Gaussian Eigenvalues" } ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() self.drawer.caption.setText("(No features selected)") # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() # Why is this necessary? # Clearing the layerstack doesn't seem to call the rowsRemoved signal? self._viewerControlWidget.featureListWidget.clear() # (Other methods already provided by our base class) ########################################### ########################################### def __init__(self, topLevelOperatorView, applet): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(topLevelOperatorView, crosshair=False) self.applet = applet self.__cleanup_fns = [] self.topLevelOperatorView.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.FeatureListFilename.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.__cleanup_fns.append( partial(self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.__cleanup_fns.append( partial( self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.onFeaturesSelectionsChanged() # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in self.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.topLevelOperatorView.Scales.setValue(self.ScalesList) self.topLevelOperatorView.FeatureIds.setValue(self.getFeatureIdOrder()) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect( self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect( self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi( os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(range(start, end + 1)): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect(handleRemovedLayers) self.layerstack.rowsInserted.connect(handleInsertedLayers) layerListWidget.currentRowChanged.connect(handleSelectionChanged) def setupLayers(self): opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "raw data" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels / numInputChannels assert 0 < featureChannelsPerInputChannel <= 3, "The feature selection Gui does not yet support features with more than three channels per input channel." for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Start.setValue(tuple(start)) opSubRegion.Stop.setValue(tuple(stop)) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, self.ScalesList) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filename = QFileDialog.getOpenFileName(self, 'Open Feature List', '.', options=options) #sanity checks on the given file if not filename: return if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return f = open(filename, 'r') with f: for line in f: line = line.strip() if len(line) == 0: continue if not os.path.exists(line): QMessageBox.critical( self, "Open Feature List", "File '%s', referenced in '%s', does not exist" % (line, filename)) return try: h = h5py.File(line, 'r') with h: assert len(h["data"].shape) == 3 except: QMessageBox.critical( self, "Open Feature List", "File '%s', referenced in '%s', could not be opened as an HDF5 file or does not contain a 3D dataset called 'data'" % (line, filename)) return self.topLevelOperatorView.FeatureListFilename.setValue(filename) self.topLevelOperatorView._setupOutputs() self.onFeaturesSelectionsChanged() def onFeatureButtonClicked(self): self.topLevelOperatorView.FeatureListFilename.setValue("") # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.topLevelOperatorView.SelectionMatrix.ready( ) and self.topLevelOperatorView.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.topLevelOperatorView.SelectionMatrix.value featureOrdering = self.topLevelOperatorView.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in self.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray( self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): self.applet.guiControlSignal.emit(ControlCommand.DisableAll) QApplication.instance().setOverrideCursor( QCursor(Qt.WaitCursor)) QApplication.instance().processEvents() opFeatureSelection.SelectionMatrix.setValue(featureMatrix) self.applet.guiControlSignal.emit(ControlCommand.Pop) QApplication.instance().restoreOverrideCursor() self.topLevelOperatorView._setupOutputs() else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption fff = ( self.topLevelOperatorView.FeatureListFilename.ready() and \ len(self.topLevelOperatorView.FeatureListFilename.value) != 0) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText("(No features selected)") self.layerstack.clear() elif fff: self.drawer.caption.setText("(features from files)") else: self.initFeatureOrder() matrix = self.topLevelOperatorView.SelectionMatrix.value self.drawer.caption.setText("(Selected %d features)" % numpy.sum(matrix))
class FeatureSelectionGui(LayerViewerGui): """ """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() # (Other methods already provided by our base class) ########################################### ########################################### def __init__(self, parentApplet, topLevelOperatorView): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(parentApplet, topLevelOperatorView, crosshair=False) self.parentApplet = parentApplet self.__cleanup_fns = [] self.topLevelOperatorView.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.topLevelOperatorView.FeatureListFilename.notifyDirty( bind(self.onFeaturesSelectionsChanged)) self.__cleanup_fns.append( partial(self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.__cleanup_fns.append( partial( self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged))) self.onFeaturesSelectionsChanged() # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in OpFeatureSelection.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.topLevelOperatorView.Scales.setValue( OpFeatureSelection.ScalesList) self.topLevelOperatorView.FeatureIds.setValue(self.getFeatureIdOrder()) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect( self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect( self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi( os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(range(start, end + 1)): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect(handleRemovedLayers) self.layerstack.rowsInserted.connect(handleInsertedLayers) layerListWidget.currentRowChanged.connect(handleSelectionChanged) # Support the same right-click menu as 'normal' layer list widgets def showLayerContextMenu(pos): idx = layerListWidget.indexAt(pos) layer = self.layerstack[idx.row()] layercontextmenu(layer, layerListWidget.mapToGlobal(pos), layerListWidget) layerListWidget.customContextMenuRequested.connect( showLayerContextMenu) layerListWidget.setContextMenuPolicy(Qt.CustomContextMenu) def setupLayers(self): opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "Raw Data (display only)" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels // numInputChannels if not 0 < featureChannelsPerInputChannel <= 3: logger.warn( "The feature selection Gui does not yet support features with more than three channels per input channel. Some features will not be displayed entirely." ) for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Roi.setValue((tuple(start), tuple(stop))) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent=self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection", "dialog size") self.featureDlg.resize(*size) except TypeError: pass def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in OpFeatureSelection.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = OpFeatureSelection.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable( groupedNames, OpFeatureSelection.ScalesList, self.topLevelOperatorView.WINDOW_SIZE) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) # Disable the first column, except for the first item. # This is a slightly hacky way of fixing ilastik issue #610. # Besides color, the features at a sigma of 0.3 are not valid because the # results are overwhelmed by the inherent sampling noise of the filter. # (This is a bit hacky because we ASSUME the first feature is Gaussian # Smoothing. It works for now.) enabled_item_mask = numpy.ones(defaultFeatures.shape, dtype=bool) enabled_item_mask[1:, 0] = False # hacky self.featureDlg.setEnableItemMask(enabled_item_mask) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filenames = QFileDialog.getOpenFileNames(self, 'Open Feature Files', '.', options=options) filenames = map(encode_from_qstring, filenames) # Check if file exists if not filenames: return for filename in filenames: if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return num_lanes = len(self.parentApplet.topLevelOperator.FeatureListFilename) if num_lanes != len(filenames): QMessageBox.critical( self, "Wrong number of feature files", "You must select all pre-computed feature files at once (shift-click).\n" "You selected {} file(s), but there are {} image(s) loaded". format(len(filenames), num_lanes)) return for filename, slot in zip( filenames, self.parentApplet.topLevelOperator.FeatureListFilename): slot.setValue(filename) # Create a dummy SelectionMatrix, just so the operator knows it is configured # This is a little hacky. We should really make SelectionMatrix optional, # and then handle the choice correctly in setupOutputs, probably involving # the Output.meta.NOTREADY flag dummy_matrix = numpy.zeros((6, 7), dtype=bool) dummy_matrix[0, 0] = True self.parentApplet.topLevelOperator.SelectionMatrix.setValue(True) # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested.emit() def onFeatureButtonClicked(self): # Remove all pre-computed feature files for slot in self.parentApplet.topLevelOperator.FeatureListFilename: slot.disconnect() # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.topLevelOperatorView.SelectionMatrix.ready( ) and self.topLevelOperatorView.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.topLevelOperatorView.SelectionMatrix.value featureOrdering = self.topLevelOperatorView.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in OpFeatureSelection.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix else: assert self.topLevelOperatorView.FeatureIds.ready() assert self.topLevelOperatorView.Scales.ready() num_rows = len(self.topLevelOperatorView.FeatureIds.value) num_cols = len(self.topLevelOperatorView.Scales.value) blank_matrix = numpy.zeros((num_rows, num_cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = blank_matrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView old_features = None if opFeatureSelection.SelectionMatrix.ready(): old_features = opFeatureSelection.SelectionMatrix.value if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray( self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): # Disable gui self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() QApplication.instance().setOverrideCursor( QCursor(Qt.WaitCursor)) QApplication.instance().processEvents() try: opFeatureSelection.SelectionMatrix.setValue(featureMatrix) except DatasetConstraintError as ex: # The user selected some scales that were too big. QMessageBox.critical(self, "Invalid selections", ex.message) if old_features is not None: opFeatureSelection.SelectionMatrix.setValue( old_features) else: opFeatureSelection.SelectionMatrix.disconnect() # Re-enable gui QApplication.instance().restoreOverrideCursor() self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested.emit() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption fff = ( self.topLevelOperatorView.FeatureListFilename.ready() and \ len(self.topLevelOperatorView.FeatureListFilename.value) != 0) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText("(No features selected)") self.layerstack.clear() elif fff: self.drawer.caption.setText("(features from files)") else: self.initFeatureOrder() matrix = self.topLevelOperatorView.SelectionMatrix.value self.drawer.caption.setText("(Selected %d features)" % numpy.sum(matrix))
class FeatureSelectionGui(LayerViewerGui): """ """ # Constants ScalesList = [0.3, 0.7, 1, 1.6, 3.5, 5.0, 10.0] DefaultColorTable = None # # Default order # FeatureIds = [ 'GaussianSmoothing', # 'LaplacianOfGaussian', # 'GaussianGradientMagnitude', # 'DifferenceOfGaussians', # 'StructureTensorEigenvalues', # 'HessianOfGaussianEigenvalues' ] # Map feature groups to lists of feature IDs FeatureGroups = [ ("Color/Intensity", ["GaussianSmoothing"]), ("Edge", [ "LaplacianOfGaussian", "GaussianGradientMagnitude", "DifferenceOfGaussians" ]), ("Texture", ["StructureTensorEigenvalues", "HessianOfGaussianEigenvalues"]) ] # Map feature IDs to feature names FeatureNames = { 'GaussianSmoothing': 'Gaussian Smoothing', 'LaplacianOfGaussian': "Laplacian of Gaussian", 'GaussianGradientMagnitude': "Gaussian Gradient Magnitude", 'DifferenceOfGaussians': "Difference of Gaussians", 'StructureTensorEigenvalues': "Structure Tensor EigenValues", 'HessianOfGaussianEigenvalues': "Hessian of Gaussian Eigenvalues" } ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawers(self): return [("Feature Selection", self.drawer)] def viewerControlWidget(self): return self._viewerControlWidget def reset(self): super(FeatureSelectionGui, self).reset() self.drawer.caption.setText("(No features selected)") # Why is this necessary? # Clearing the layerstack doesn't seem to call the rowsRemoved signal? self._viewerControlWidget.listWidget.clear() # (Other methods already provided by our base class) ########################################### ########################################### @traceLogged(traceLogger) def __init__(self, mainOperator): """ """ super(FeatureSelectionGui, self).__init__(mainOperator) self.mainOperator = mainOperator self.mainOperator.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged)) # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in self.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.mainOperator.Scales.setValue(self.ScalesList) self.mainOperator.FeatureIds.setValue(self.getFeatureIdOrder()) @traceLogged(traceLogger) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir + "/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect( self.onFeatureButtonClicked) @traceLogged(traceLogger) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi( os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.listWidget # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) def handleInsertedLayers(parent, start, end): for i in range(start, end + 1): layerListWidget.insertItem(i, self.layerstack[i].name) self.layerstack.rowsInserted.connect(handleInsertedLayers) def handleRemovedLayers(parent, start, end): for i in reversed(range(start, end + 1)): layerListWidget.takeItem(i) self.layerstack.rowsRemoved.connect(handleRemovedLayers) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) layerListWidget.currentRowChanged.connect(handleSelectionChanged) @traceLogged(traceLogger) def setupLayers(self, currentImageIndex): layers = [] opFeatureSelection = self.operatorForCurrentImage() inputSlot = opFeatureSelection.InputImage featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers @traceLogged(traceLogger) def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels / numInputChannels assert 0 < featureChannelsPerInputChannel <= 3, "The feature selection Gui does not yet support features with more than three channels per input channel." for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(graph=self.mainOperator.graph) opSubRegion.Input.connect(featureSlot) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel + 1) * featureChannelsPerInputChannel opSubRegion.Start.setValue(tuple(start)) opSubRegion.Stop.setValue(tuple(stop)) featureLayer = self.createStandardLayerFromSlot(opSubRegion.Output) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers @traceLogged(traceLogger) def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg() self.featureDlg.setWindowTitle("Features") # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in self.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = self.FeatureNames[featureId] featureEntries.append(FeatureEntry(featureName)) groupedNames.append((group, featureEntries)) self.featureDlg.createFeatureTable(groupedNames, self.ScalesList) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.mainOperator.FeatureIds.value) cols = len(self.mainOperator.Scales.value) defaultFeatures = numpy.zeros((rows, cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) def onFeatureButtonClicked(self): # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.mainOperator.SelectionMatrix.ready( ) and self.mainOperator.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.mainOperator.SelectionMatrix.value featureOrdering = self.mainOperator.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in self.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.operatorForCurrentImage() if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray( self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): opFeatureSelection.SelectionMatrix.setValue(featureMatrix) else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption if not self.mainOperator.SelectionMatrix.ready(): self.drawer.caption.setText("(No features selected)") self.layerstack.clear() else: self.initFeatureOrder() matrix = self.mainOperator.SelectionMatrix.value self.drawer.caption.setText("(Selected %d features)" % numpy.sum(matrix))
class FeatureSelectionGui(LayerViewerGui): """ """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def appletDrawer(self): return self.drawer def viewerControlWidget(self): return self._viewerControlWidget def stopAndCleanUp(self): super(FeatureSelectionGui, self).stopAndCleanUp() # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() # (Other methods already provided by our base class) ########################################### ########################################### def __init__(self, parentApplet, topLevelOperatorView): """ """ self.topLevelOperatorView = topLevelOperatorView super(FeatureSelectionGui, self).__init__(parentApplet, topLevelOperatorView, crosshair=False) self.parentApplet = parentApplet self.__cleanup_fns = [] self.topLevelOperatorView.SelectionMatrix.notifyDirty( bind(self.onFeaturesSelectionsChanged) ) self.topLevelOperatorView.FeatureListFilename.notifyDirty( bind(self.onFeaturesSelectionsChanged) ) self.__cleanup_fns.append( partial( self.topLevelOperatorView.SelectionMatrix.unregisterDirty, bind(self.onFeaturesSelectionsChanged) ) ) self.__cleanup_fns.append( partial( self.topLevelOperatorView.FeatureListFilename.unregisterDirty, bind(self.onFeaturesSelectionsChanged) ) ) self.onFeaturesSelectionsChanged() # Init feature dialog self.initFeatureDlg() def getFeatureIdOrder(self): featureIrdOrder = [] for group, featureIds in OpFeatureSelection.FeatureGroups: featureIrdOrder += featureIds return featureIrdOrder def initFeatureOrder(self): self.topLevelOperatorView.Scales.setValue( OpFeatureSelection.ScalesList ) self.topLevelOperatorView.FeatureIds.setValue( self.getFeatureIdOrder() ) def initAppletDrawerUi(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] # (We don't pass self here because we keep the drawer ui in a separate object.) self.drawer = uic.loadUi(localDir+"/featureSelectionDrawer.ui") self.drawer.SelectFeaturesButton.clicked.connect(self.onFeatureButtonClicked) self.drawer.UsePrecomputedFeaturesButton.clicked.connect(self.onUsePrecomputedFeaturesButtonClicked) dbg = ilastik_config.getboolean("ilastik", "debug") if not dbg: self.drawer.UsePrecomputedFeaturesButton.setHidden(True) def initViewerControlUi(self): """ Load the viewer controls GUI, which appears below the applet bar. In our case, the viewer control GUI consists mainly of a layer list. TODO: Right now we manage adding/removing entries to a plain listview widget by monitoring the layerstack for changes. Ideally, we should implement a custom widget that does this for us, which would be initialized with the layer list model (like volumina.layerwidget) """ self._viewerControlWidget = uic.loadUi(os.path.split(__file__)[0] + "/viewerControls.ui") layerListWidget = self._viewerControlWidget.featureListWidget layerListWidget.setSelectionMode(QAbstractItemView.SingleSelection) # Need to handle data changes because the layerstack model hasn't # updated his data yet by the time he calls the rowsInserted signal def handleLayerStackDataChanged(startIndex, stopIndex): row = startIndex.row() layerListWidget.item(row).setText(self.layerstack[row].name) def handleSelectionChanged(row): # Only one layer is visible at a time for i, layer in enumerate(self.layerstack): layer.visible = (i == row) def handleInsertedLayers(parent, start, end): for i in range(start, end+1): layerListWidget.insertItem(i, self.layerstack[i].name) if layerListWidget.model().rowCount() == 1: layerListWidget.item(0).setSelected(True) def handleRemovedLayers(parent, start, end): for i in reversed(list(range(start, end+1))): layerListWidget.takeItem(i) self.layerstack.dataChanged.connect(handleLayerStackDataChanged) self.layerstack.rowsRemoved.connect( handleRemovedLayers ) self.layerstack.rowsInserted.connect( handleInsertedLayers ) layerListWidget.currentRowChanged.connect( handleSelectionChanged ) # Support the same right-click menu as 'normal' layer list widgets def showLayerContextMenu( pos ): idx = layerListWidget.indexAt(pos) layer = self.layerstack[idx.row()] layercontextmenu( layer, layerListWidget.mapToGlobal(pos), layerListWidget ) layerListWidget.customContextMenuRequested.connect( showLayerContextMenu ) layerListWidget.setContextMenuPolicy( Qt.CustomContextMenu ) def setupLayers(self): opFeatureSelection = self.topLevelOperatorView inputSlot = opFeatureSelection.InputImage layers = [] if inputSlot.ready(): rawLayer = self.createStandardLayerFromSlot(inputSlot) rawLayer.visible = True rawLayer.opacity = 1.0 rawLayer.name = "Raw Data (display only)" layers.append(rawLayer) featureMultiSlot = opFeatureSelection.FeatureLayers if inputSlot.ready() and featureMultiSlot.ready(): for featureIndex, featureSlot in enumerate(featureMultiSlot): assert featureSlot.ready() layers += self.getFeatureLayers(inputSlot, featureSlot) layers[0].visible = True return layers def getFeatureLayers(self, inputSlot, featureSlot): """ Generate a list of layers for the feature image produced by the given slot. """ layers = [] channelAxis = inputSlot.meta.axistags.channelIndex assert channelAxis == featureSlot.meta.axistags.channelIndex numInputChannels = inputSlot.meta.shape[channelAxis] numFeatureChannels = featureSlot.meta.shape[channelAxis] # Determine how many channels this feature has (up to 3) featureChannelsPerInputChannel = numFeatureChannels // numInputChannels if not 0 < featureChannelsPerInputChannel <= 3: logger.warning( "The feature selection Gui does not yet support features with more than three channels per input channel. Some features will not be displayed entirely." ) for inputChannel in range(numInputChannels): # Determine the name for this feature featureName = featureSlot.meta.description assert featureName is not None if 2 <= numInputChannels <= 3: channelNames = ['R', 'G', 'B'] featureName += " (" + channelNames[inputChannel] + ")" if numInputChannels > 3: featureName += " (Ch. {})".format(inputChannel) opSubRegion = OpSubRegion(parent=self.topLevelOperatorView.parent) opSubRegion.Input.connect( featureSlot ) start = [0] * len(featureSlot.meta.shape) start[channelAxis] = inputChannel * featureChannelsPerInputChannel stop = list(featureSlot.meta.shape) stop[channelAxis] = (inputChannel+1) * featureChannelsPerInputChannel opSubRegion.Roi.setValue( (tuple(start), tuple(stop)) ) featureLayer = self.createStandardLayerFromSlot( opSubRegion.Output ) featureLayer.visible = False featureLayer.opacity = 1.0 featureLayer.name = featureName layers.append(featureLayer) return layers def initFeatureDlg(self): """ Initialize the feature selection widget. """ self.initFeatureOrder() self.featureDlg = FeatureDlg(parent = self) self.featureDlg.setWindowTitle("Features") try: size = PreferencesManager().get("featureSelection","dialog size") self.featureDlg.resize(*size) except TypeError:pass def saveSize(): size = self.featureDlg.size() s = (size.width(),size.height()) PreferencesManager().set("featureSelection","dialog size",s) self.featureDlg.accepted.connect(saveSize) # Map from groups of feature IDs to groups of feature NAMEs groupedNames = [] for group, featureIds in OpFeatureSelection.FeatureGroups: featureEntries = [] for featureId in featureIds: featureName = OpFeatureSelection.FeatureNames[featureId] featureEntries.append( FeatureEntry(featureName) ) groupedNames.append( (group, featureEntries) ) self.featureDlg.createFeatureTable( groupedNames, OpFeatureSelection.ScalesList, self.topLevelOperatorView.WINDOW_SIZE ) self.featureDlg.setImageToPreView(None) # Init with no features rows = len(self.topLevelOperatorView.FeatureIds.value) cols = len(self.topLevelOperatorView.Scales.value) defaultFeatures = numpy.zeros((rows,cols), dtype=bool) self.featureDlg.selectedFeatureBoolMatrix = defaultFeatures self.featureDlg.accepted.connect(self.onNewFeaturesFromFeatureDlg) # Disable the first column, except for the first item. # This is a slightly hacky way of fixing ilastik issue #610. # Besides color, the features at a sigma of 0.3 are not valid because the # results are overwhelmed by the inherent sampling noise of the filter. # (This is a bit hacky because we ASSUME the first feature is Gaussian # Smoothing. It works for now.) enabled_item_mask = numpy.ones( defaultFeatures.shape, dtype=bool ) enabled_item_mask[1:,0] = False # hacky self.featureDlg.setEnableItemMask( enabled_item_mask ) def onUsePrecomputedFeaturesButtonClicked(self): options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog filenames, _filter = QFileDialog.getOpenFileNames(self, 'Open Feature Files', '.', options=options) # Check if file exists if not filenames: return for filename in filenames: if not os.path.exists(filename): QMessageBox.critical(self, "Open Feature List", "File '%s' does not exist" % filename) return num_lanes = len(self.parentApplet.topLevelOperator.FeatureListFilename) if num_lanes != len(filenames): QMessageBox.critical(self, "Wrong number of feature files", "You must select all pre-computed feature files at once (shift-click).\n" "You selected {} file(s), but there are {} image(s) loaded" .format(len(filenames), num_lanes)) return for filename, slot in zip(filenames, self.parentApplet.topLevelOperator.FeatureListFilename): slot.setValue(filename) # Create a dummy SelectionMatrix, just so the operator knows it is configured # This is a little hacky. We should really make SelectionMatrix optional, # and then handle the choice correctly in setupOutputs, probably involving # the Output.meta.NOTREADY flag dummy_matrix = numpy.zeros((6,7), dtype=bool) dummy_matrix[0,0] = True self.parentApplet.topLevelOperator.SelectionMatrix.setValue(True) # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeatureButtonClicked(self): # Remove all pre-computed feature files for slot in self.parentApplet.topLevelOperator.FeatureListFilename: slot.disconnect() # Refresh the feature matrix in case it has changed since the last time we were opened # (e.g. if the user loaded a project from disk) if self.topLevelOperatorView.SelectionMatrix.ready() and self.topLevelOperatorView.FeatureIds.ready(): # Re-order the feature matrix using the loaded feature ids matrix = self.topLevelOperatorView.SelectionMatrix.value featureOrdering = self.topLevelOperatorView.FeatureIds.value reorderedMatrix = numpy.zeros(matrix.shape, dtype=bool) newrow = 0 for group, featureIds in OpFeatureSelection.FeatureGroups: for featureId in featureIds: oldrow = featureOrdering.index(featureId) reorderedMatrix[newrow] = matrix[oldrow] newrow += 1 self.featureDlg.selectedFeatureBoolMatrix = reorderedMatrix else: assert self.topLevelOperatorView.FeatureIds.ready() assert self.topLevelOperatorView.Scales.ready() num_rows = len(self.topLevelOperatorView.FeatureIds.value) num_cols = len(self.topLevelOperatorView.Scales.value) blank_matrix = numpy.zeros( (num_rows, num_cols), dtype=bool ) self.featureDlg.selectedFeatureBoolMatrix = blank_matrix # Now open the feature selection dialog self.featureDlg.exec_() def onNewFeaturesFromFeatureDlg(self): opFeatureSelection = self.topLevelOperatorView old_features = None if opFeatureSelection.SelectionMatrix.ready(): old_features = opFeatureSelection.SelectionMatrix.value if opFeatureSelection is not None: # Re-initialize the scales and features self.initFeatureOrder() # Give the new features to the pipeline (if there are any) featureMatrix = numpy.asarray(self.featureDlg.selectedFeatureBoolMatrix) if featureMatrix.any(): # Disable gui self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested() QApplication.instance().setOverrideCursor( QCursor(Qt.WaitCursor) ) try: opFeatureSelection.SelectionMatrix.setValue( featureMatrix ) except DatasetConstraintError as ex: # The user selected some scales that were too big. QMessageBox.critical(self, "Invalid selections", ex.message) if old_features is not None: opFeatureSelection.SelectionMatrix.setValue( old_features ) else: opFeatureSelection.SelectionMatrix.disconnect() # Re-enable gui QApplication.instance().restoreOverrideCursor() self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested() else: # Not valid to give a matrix with no features selected. # Disconnect. opFeatureSelection.SelectionMatrix.disconnect() # Notify the workflow that some applets may have changed state now. # (For example, the downstream pixel classification applet can # be used now that there are features selected) self.parentApplet.appletStateUpdateRequested() def onFeaturesSelectionsChanged(self): """ Handles changes to our top-level operator's matrix of feature selections. """ # Update the drawer caption fff = ( self.topLevelOperatorView.FeatureListFilename.ready() and \ len(self.topLevelOperatorView.FeatureListFilename.value) != 0) if not self.topLevelOperatorView.SelectionMatrix.ready() and not fff: self.drawer.caption.setText( "(No features selected)" ) self.layerstack.clear() elif fff: self.drawer.caption.setText( "(features from files)" ) else: self.initFeatureOrder() matrix = self.topLevelOperatorView.SelectionMatrix.value self.drawer.caption.setText( "(Selected %d features)" % numpy.sum(matrix) )