Source code for ilastik.applets.objectClassification.objectClassificationGui

###############################################################################
#   ilastik: interactive learning and segmentation toolkit
#
#       Copyright (C) 2011-2014, the ilastik developers
#                                <team@ilastik.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# In addition, as a special exception, the copyright holders of
# ilastik give you permission to combine ilastik with applets,
# workflows and plugins which are not covered under the GNU
# General Public License.
#
# See the LICENSE file for details. License information is also available
# on the ilastik web site at:
#		   http://ilastik.org/license.html
###############################################################################
from PyQt4.QtGui import *
from PyQt4 import uic
from PyQt4.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, pyqtBoundSignal
from PyQt4.QtGui import QFileDialog
from ilastik.shell.gui.ipcManager import IPCFacade, Protocol

from ilastik.widgets.featureTableWidget import FeatureEntry
from ilastik.widgets.featureDlg import FeatureDlg
from ilastik.widgets.exportObjectInfoDialog import ExportObjectInfoDialog
from ilastik.applets.objectExtraction.opObjectExtraction import default_features_key
from ilastik.applets.objectClassification.opObjectClassification import OpObjectClassification

import os
import copy
import vigra

import numpy
import weakref
from functools import partial

from ilastik.config import cfg as ilastik_config
from ilastik.utility import bind
from ilastik.utility.gui import ThreadRouter, threadRouted
from ilastik.plugins import pluginManager

import logging
logger = logging.getLogger(__name__)

from ilastik.applets.layerViewer.layerViewerGui import LayerViewerGui
from ilastik.applets.labeling.labelingGui import LabelingGui

import volumina.colortables as colortables
from volumina.api import \
    LazyflowSource, GrayscaleLayer, ColortableLayer, AlphaModulatedLayer, \
    ClickableColortableLayer, LazyflowSinkSource
from volumina.utility import encode_from_qstring

from volumina.interpreter import ClickInterpreter
from volumina.utility import ShortcutManager


def _listReplace(old, new):
    if len(old) > len(new):
        return new + old[len(new):]
    else:
        return new

from ilastik.applets.objectExtraction.objectExtractionGui import FeatureSelectionDialog


class FeatureSubSelectionDialog(FeatureSelectionDialog):
    def __init__(self, featureDict, selectedFeatures=None, parent=None, ndim=3):
        super(FeatureSubSelectionDialog, self).__init__(featureDict, selectedFeatures, parent, ndim)
        self.setObjectName("FeatureSubSelectionDialog")
        self.ui.spinBox_X.setEnabled(False)
        self.ui.spinBox_Y.setEnabled(False)
        self.ui.spinBox_Z.setEnabled(False)
        self.ui.spinBox_X.setVisible(False)
        self.ui.spinBox_Y.setVisible(False)
        self.ui.spinBox_Z.setVisible(False)
        self.ui.marginLabel.setVisible(False)
        self.ui.label.setVisible(False)
        self.ui.label_2.setVisible(False)
        self.ui.label_z.setVisible(False)

[docs]class ObjectClassificationGui(LabelingGui): """A subclass of LabelingGui for labeling objects. Handles labeling objects, viewing the predicted results, and displaying warnings from the top level operator. Also provides a dialog for choosing subsets of the precalculated features provided by the object extraction applet. """ def centralWidget(self): return self def stopAndCleanUp(self): # Unsubscribe to all signals for fn in self.__cleanup_fns: fn() # Base class super(ObjectClassificationGui, self).stopAndCleanUp() PREDICTION_LAYER_NAME = "Prediction" #FIXME #temporary place for this operator, move somewhere else later _knime_exporter = None def __init__(self, parentApplet, op): self.__cleanup_fns = [] # Tell our base class which slots to monitor labelSlots = LabelingGui.LabelingSlots() labelSlots.labelInput = op.LabelInputs labelSlots.labelOutput = op.LabelImages labelSlots.labelEraserValue = op.Eraser labelSlots.labelDelete = op.DeleteLabel labelSlots.maxLabelValue = op.NumLabels labelSlots.labelNames = op.LabelNames # We provide our own UI file (which adds an extra control for # interactive mode) This UI file is copied from # pixelClassification pipeline # labelingDrawerUiPath = os.path.split(__file__)[0] + '/labelingDrawer.ui' # Base class init super(ObjectClassificationGui, self).__init__(parentApplet, labelSlots, op, labelingDrawerUiPath, crosshair=False) self.op = op self.applet = parentApplet self.threadRouter = ThreadRouter(self) op.Warnings.notifyDirty(self.handleWarnings) self.__cleanup_fns.append( partial( op.Warnings.unregisterDirty, self.handleWarnings ) ) self._retained_weakrefs = [] # unused self.labelingDrawerUi.savePredictionsButton.setEnabled(False) self.labelingDrawerUi.savePredictionsButton.setVisible(False) self.labelingDrawerUi.brushSizeComboBox.setEnabled(False) self.labelingDrawerUi.brushSizeComboBox.setVisible(False) self.labelingDrawerUi.brushSizeCaption.setVisible(False) self._colorTable16_forpmaps = self._createDefault16ColorColorTable() self._colorTable16_forpmaps[15] = QColor(Qt.black).rgba() #for objects with NaNs in features # button handlers self._interactiveMode = False self._showPredictions = False self._labelMode = True self.labelingDrawerUi.subsetFeaturesButton.clicked.connect( self.handleSubsetFeaturesClicked) self.labelingDrawerUi.checkInteractive.toggled.connect( self.handleInteractiveModeClicked) self.labelingDrawerUi.checkShowPredictions.toggled.connect( self.handleShowPredictionsClicked) #select all the features in the beginning cfn = None already_selected = None if self.op.ComputedFeatureNames.ready(): cfn = self.op.ComputedFeatureNames[:].wait() if self.op.SelectedFeatures.ready(): already_selected = self.op.SelectedFeatures[:].wait() if already_selected is None or len(already_selected)==0: if cfn is not None: already_selected = cfn self.op.SelectedFeatures.setValue(already_selected) nfeatures = 0 if already_selected is not None: for plugin_features in already_selected.itervalues(): nfeatures += len(plugin_features) self.labelingDrawerUi.featuresSubset.setText("{} features selected,\nsome may have multiple channels".format(nfeatures)) # enable/disable buttons logic self.op.ObjectFeatures.notifyDirty(bind(self.checkEnableButtons)) self.__cleanup_fns.append( partial( op.ObjectFeatures.unregisterDirty, bind(self.checkEnableButtons) ) ) self.op.NumLabels.notifyReady(bind(self.checkEnableButtons)) self.__cleanup_fns.append( partial( op.NumLabels.unregisterReady, bind(self.checkEnableButtons) ) ) self.op.NumLabels.notifyDirty(bind(self.checkEnableButtons)) self.__cleanup_fns.append( partial( op.NumLabels.unregisterDirty, bind(self.checkEnableButtons) ) ) self.op.SelectedFeatures.notifyDirty(bind(self.checkEnableButtons)) self.__cleanup_fns.append( partial( op.SelectedFeatures.unregisterDirty, bind(self.checkEnableButtons) ) ) if not self.op.AllowAddLabel([]).wait()[0]: self.labelingDrawerUi.AddLabelButton.hide() self.labelingDrawerUi.AddLabelButton.clicked.disconnect() self.badObjectBox = None self.checkEnableButtons() def menus(self): m = QMenu("&Export", self.volumeEditorWidget) #m.addAction("Export Object Information").triggered.connect(self.show_export_dialog) if ilastik_config.getboolean("ilastik", "debug"): m.addAction("Export All Label Info").triggered.connect( self.exportLabelInfo ) m.addAction("Import New Label Info").triggered.connect( self.importLabelInfo ) return [m] def exportLabelInfo(self): file_path = QFileDialog.getSaveFileName(parent=self, caption="Export Label Info as JSON", filter="*.json") topLevelOp = self.topLevelOperatorView.viewed_operator() topLevelOp.exportLabelInfo(file_path) def importLabelInfo(self): file_path = QFileDialog.getOpenFileName(parent=self, caption="Export Label Info as JSON", filter="*.json") topLevelOp = self.topLevelOperatorView.viewed_operator() topLevelOp.importLabelInfo(file_path) @property def labelMode(self): return self._labelMode @labelMode.setter def labelMode(self, val): self.labelingDrawerUi.labelListView.allowDelete = ( val and self.op.AllowDeleteLabels([]).wait()[0] ) self.labelingDrawerUi.AddLabelButton.setEnabled(val) self._labelMode = val @property def interactiveMode(self): return self._interactiveMode @interactiveMode.setter def interactiveMode(self, val): logger.debug("setting interactive mode to '%r'" % val) self._interactiveMode = val self.labelingDrawerUi.checkInteractive.setChecked(val) if val: self.showPredictions = True self.labelMode = not val self.op.FreezePredictions.setValue(not val) @pyqtSlot() def handleInteractiveModeClicked(self): self.interactiveMode = self.labelingDrawerUi.checkInteractive.isChecked() @property def showPredictions(self): return self._showPredictions @showPredictions.setter def showPredictions(self, val): self._showPredictions = val self.labelingDrawerUi.checkShowPredictions.setChecked(val) for layer in self.layerstack: if "Prediction" in layer.name: layer.visible = val if self.labelMode and not val: self.labelMode = False # And hide all segmentation layers for layer in self.layerstack: if "Segmentation" in layer.name: layer.visible = False @pyqtSlot() def handleShowPredictionsClicked(self): self.showPredictions = self.labelingDrawerUi.checkShowPredictions.isChecked() @pyqtSlot() def handleSubsetFeaturesClicked(self): mainOperator = self.topLevelOperatorView computedFeatures = copy.deepcopy(mainOperator.ComputedFeatureNames([]).wait()) # do NOT show default features, the user did not want them for classification # the key for the fake plugin of default features is taken from the top of opObjectExtraction file if mainOperator.SelectedFeatures.ready(): selectedFeatures = copy.deepcopy(mainOperator.SelectedFeatures([]).wait()) else: selectedFeatures = computedFeatures plugins = pluginManager.getPluginsOfCategory('ObjectFeatures') taggedShape = mainOperator.RawImages.meta.getTaggedShape() fakeimgshp = [taggedShape['x'], taggedShape['y']] fakelabelsshp = [taggedShape['x'], taggedShape['y']] if 'z' in taggedShape and taggedShape['z']>1: fakeimgshp.append(taggedShape['z']) fakelabelsshp.append(taggedShape['z']) ndim = 3 else: ndim = 2 if 'c' in taggedShape and taggedShape['c']>1: fakeimgshp.append(taggedShape['c']) fakeimg = numpy.empty(fakeimgshp, dtype=numpy.float32) fakelabels = numpy.empty(fakelabelsshp, dtype=numpy.uint32) if ndim==3: fakelabels = vigra.taggedView(fakelabels, 'xyz') if len(fakeimgshp)==4: fakeimg = vigra.taggedView(fakeimg, 'xyzc') else: fakeimg = vigra.taggedView(fakeimg, 'xyz') if ndim==2: fakelabels = vigra.taggedView(fakelabels, 'xy') if len(fakeimgshp)==3: fakeimg = vigra.taggedView(fakeimg, 'xyc') else: fakeimg = vigra.taggedView(fakeimg, 'xy') for pluginInfo in plugins: availableFeatures = pluginInfo.plugin_object.availableFeatures(fakeimg, fakelabels) if len(availableFeatures) > 0: if pluginInfo.name in self.applet._selectedFeatures.keys(): assert pluginInfo.name in computedFeatures.keys(), 'Object Classification: {} not found in available (computed) object features'.format(pluginInfo.name) if not pluginInfo.name in selectedFeatures and pluginInfo.name in self.applet._selectedFeatures: selectedFeatures[pluginInfo.name]=dict() for feature in self.applet._selectedFeatures[pluginInfo.name].keys(): if feature in availableFeatures.keys(): selectedFeatures[pluginInfo.name][feature] = availableFeatures[feature] dlg = FeatureSubSelectionDialog(computedFeatures, selectedFeatures=selectedFeatures, ndim=ndim) dlg.exec_() if dlg.result() == QDialog.Accepted: if len(dlg.selectedFeatures) == 0: self.interactiveMode = False mainOperator.SelectedFeatures.setValue(dlg.selectedFeatures) nfeatures = 0 for plugin_features in dlg.selectedFeatures.itervalues(): nfeatures += len(plugin_features) self.labelingDrawerUi.featuresSubset.setText("{} features selected,\nsome may have multiple channels".format(nfeatures)) mainOperator.ComputedFeatureNames.setDirty(()) @pyqtSlot() def checkEnableButtons(self): feats_enabled = True predict_enabled = True labels_enabled = True if self.op.ComputedFeatureNames.ready(): featnames = self.op.ComputedFeatureNames([]).wait() if len(featnames) == 0: feats_enabled = False else: feats_enabled = False if feats_enabled: if self.op.SelectedFeatures.ready(): featnames = self.op.SelectedFeatures([]).wait() if len(featnames) == 0: predict_enabled = False else: predict_enabled = False if self.op.NumLabels.ready(): if self.op.NumLabels.value < 2: predict_enabled = False else: predict_enabled = False else: predict_enabled = False if not predict_enabled: self.interactiveMode = False self.showPredictions = False self.labelingDrawerUi.subsetFeaturesButton.setEnabled(feats_enabled) self.labelingDrawerUi.checkInteractive.setEnabled(predict_enabled) self.labelingDrawerUi.checkShowPredictions.setEnabled(predict_enabled) self.labelingDrawerUi.AddLabelButton.setEnabled(labels_enabled) self.labelingDrawerUi.labelListView.allowDelete = ( True and self.op.AllowDeleteLabels([]).wait()[0] ) self.allowDeleteLastLabelOnly(False or self.op.AllowDeleteLastLabelOnly([]).wait()[0]) self.op._predict_enabled = predict_enabled self.applet.appletStateUpdateRequested.emit()
[docs] 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+"/drawer.ui")
### Function dealing with label name and color consistency def _getNext(self, slot, parentFun, transform=None): numLabels = self.labelListData.rowCount() value = slot.value if numLabels < len(value): result = value[numLabels] if transform is not None: result = transform(result) return result else: return parentFun() def _onLabelChanged(self, parentFun, mapf, slot): parentFun() new = map(mapf, self.labelListData) old = slot.value slot.setValue(_listReplace(old, new)) def _getNextSuggestedLabelName(self): row_idx = self._labelControlUi.labelListModel.rowCount() return self.topLevelOperatorView.SuggestedLabelNames([]).wait()[row_idx] def getNextLabelName(self): if self._labelControlUi.labelListModel.rowCount() >= len(self.topLevelOperatorView.SuggestedLabelNames([]).wait()): return self._getNext(self.topLevelOperatorView.LabelNames, super(ObjectClassificationGui, self).getNextLabelName) else: return self._getNext(self.topLevelOperatorView.LabelNames, self._getNextSuggestedLabelName) def getNextLabelColor(self): return self._getNext( self.topLevelOperatorView.LabelColors, super(ObjectClassificationGui, self).getNextLabelColor, lambda x: QColor(*x) ) def getNextPmapColor(self): return self._getNext( self.topLevelOperatorView.PmapColors, super(ObjectClassificationGui, self).getNextPmapColor, lambda x: QColor(*x) ) def onLabelNameChanged(self): self._onLabelChanged(super(ObjectClassificationGui, self).onLabelNameChanged, lambda l: l.name, self.topLevelOperatorView.LabelNames) def onLabelColorChanged(self): self._onLabelChanged(super(ObjectClassificationGui, self).onLabelColorChanged, lambda l: (l.brushColor().red(), l.brushColor().green(), l.brushColor().blue()), self.topLevelOperatorView.LabelColors) def onPmapColorChanged(self): self._onLabelChanged(super(ObjectClassificationGui, self).onPmapColorChanged, lambda l: (l.pmapColor().red(), l.pmapColor().green(), l.pmapColor().blue()), self.topLevelOperatorView.PmapColors) def _onLabelRemoved(self, parent, start, end): # Don't respond unless this actually came from the GUI if self._programmaticallyRemovingLabels: return # update the pmap colors. copied from labelingGui._onLabelRemoved # Remove the deleted label's color from the color table so that renumbered labels keep their colors. oldcount = self._labelControlUi.labelListModel.rowCount() + 1 oldColor = self._colorTable16_forpmaps.pop(start+1) # Recycle the deleted color back into the table (for the next label to be added) self._colorTable16_forpmaps.insert(oldcount, oldColor) # Find the prediction layer and update its colortable layer_index = self.layerstack.findMatchingIndex(lambda x: x.name == self.PREDICTION_LAYER_NAME) predictLayer = self.layerstack[layer_index] predictLayer.colorTable = self._colorTable16_forpmaps # Base class super(ObjectClassificationGui, self)._onLabelRemoved(parent, start, end) op = self.topLevelOperatorView op.removeLabel(start) # Keep colors in sync with names # (If we deleted a name, delete its corresponding colors, too.) if len(op.PmapColors.value) > len(op.LabelNames.value): for slot in (op.LabelColors, op.PmapColors): value = slot.value value.pop(start) # Force dirty propagation even though the list id is unchanged. slot.setValue(value, check_changed=False)
[docs] def createLabelLayer(self, direct=False): """Return a colortable layer that displays the label slot data, along with its associated label source. direct: whether this layer is drawn synchronously by volumina """ labelInput = self._labelingSlots.labelInput labelOutput = self._labelingSlots.labelOutput if not labelOutput.ready(): return (None, None) else: self._colorTable16[15] = QColor(Qt.black).rgba() #for the objects with NaNs in features labelsrc = LazyflowSinkSource(labelOutput, labelInput) labellayer = ColortableLayer(labelsrc, colorTable=self._colorTable16, direct=direct) labellayer.segmentationImageSlot = self.op.SegmentationImagesOut labellayer.name = "Labels" labellayer.ref_object = None labellayer.zeroIsTransparent = False labellayer.colortableIsRandom = True clickInt = ClickInterpreter(self.editor, labellayer, self.onClick, right=False, double=False) self.editor.brushingInterpreter = clickInt return labellayer, labelsrc
def setupLayers(self): # Base class provides the label layer. layers = super(ObjectClassificationGui, self).setupLayers() binarySlot = self.op.BinaryImages segmentedSlot = self.op.SegmentationImages rawSlot = self.op.RawImages #This is just for colors labels = self.labelListData for channel, probSlot in enumerate(self.op.PredictionProbabilityChannels): if probSlot.ready() and channel < len(labels): ref_label = labels[channel] probsrc = LazyflowSource(probSlot) probLayer = AlphaModulatedLayer( probsrc, tintColor=ref_label.pmapColor(), range=(0.0, 1.0), normalize=(0.0, 1.0) ) probLayer.opacity = 0.25 #probLayer.visible = self.labelingDrawerUi.checkInteractive.isChecked() #False, because it's much faster to draw predictions without these layers below probLayer.visible = False probLayer.setToolTip("Probability that the object belongs to class {}".format(channel+1)) def setLayerColor(c, predictLayer_=probLayer, ch=channel, initializing=False): if not initializing and predictLayer_ not in self.layerstack: # This layer has been removed from the layerstack already. # Don't touch it. return predictLayer_.tintColor = c def setLayerName(n, predictLayer_=probLayer, initializing=False): if not initializing and predictLayer_ not in self.layerstack: # This layer has been removed from the layerstack already. # Don't touch it. return newName = "Prediction for %s" % n predictLayer_.name = newName setLayerName(ref_label.name, initializing=True) ref_label.pmapColorChanged.connect(setLayerColor) ref_label.nameChanged.connect(setLayerName) layers.append(probLayer) predictionSlot = self.op.PredictionImages if predictionSlot.ready(): predictsrc = LazyflowSource(predictionSlot) self._colorTable16_forpmaps[0] = 0 predictLayer = ColortableLayer(predictsrc, colorTable=self._colorTable16_forpmaps) predictLayer.name = self.PREDICTION_LAYER_NAME predictLayer.ref_object = None predictLayer.visible = self.labelingDrawerUi.checkInteractive.isChecked() predictLayer.opacity = 0.5 predictLayer.setToolTip("Classification results, assigning a label to each object") # This weakref stuff is a little more fancy than strictly necessary. # The idea is to use the weakref's callback to determine when this layer instance is destroyed by the garbage collector, # and then we disconnect the signal that updates that layer. weak_predictLayer = weakref.ref( predictLayer ) colortable_changed_callback = bind( self._setPredictionColorTable, weak_predictLayer ) self._labelControlUi.labelListModel.dataChanged.connect( colortable_changed_callback ) weak_predictLayer2 = weakref.ref( predictLayer, partial(self._disconnect_dataChange_callback, colortable_changed_callback) ) # We have to make sure the weakref isn't destroyed because it is responsible for calling the callback. # Therefore, we retain it by adding it to a list. self._retained_weakrefs.append( weak_predictLayer2 ) # Ensure we're up-to-date (in case this is the first time the prediction layer is being added. for row in range( self._labelControlUi.labelListModel.rowCount() ): self._setPredictionColorTableForRow( predictLayer, row ) # put right after Labels, so that it is visible after hitting "live # predict". layers.insert(1, predictLayer) badObjectsSlot = self.op.BadObjectImages if badObjectsSlot.ready(): ct_black = [0, QColor(Qt.black).rgba()] badSrc = LazyflowSource(badObjectsSlot) badLayer = ColortableLayer(badSrc, colorTable = ct_black) badLayer.name = "Ambiguous objects" badLayer.setToolTip("Objects with infinite or invalid values in features") badLayer.visible = False layers.append(badLayer) if segmentedSlot.ready(): ct = colortables.create_default_16bit() objectssrc = LazyflowSource(segmentedSlot) ct[0] = QColor(0, 0, 0, 0).rgba() # make 0 transparent objLayer = ColortableLayer(objectssrc, ct) objLayer.name = "Objects" objLayer.opacity = 0.5 objLayer.visible = False objLayer.setToolTip("Segmented objects (labeled image/connected components)") layers.append(objLayer) if binarySlot.ready(): ct_binary = [0, QColor(255, 255, 255, 255).rgba()] # white foreground on transparent background, even for labeled images binct = [QColor(255, 255, 255, 255).rgba()]*65536 binct[0] = 0 binaryimagesrc = LazyflowSource(binarySlot) binLayer = ColortableLayer(binaryimagesrc, binct) binLayer.name = "Binary image" binLayer.visible = True binLayer.opacity = 1.0 binLayer.setToolTip("Segmentation results as a binary mask") layers.append(binLayer) if rawSlot.ready(): rawLayer = self.createStandardLayerFromSlot(rawSlot) rawLayer.name = "Raw data" def toggleTopToBottom(): index = self.layerstack.layerIndex( rawLayer ) self.layerstack.selectRow( index ) if index == 0: self.layerstack.moveSelectedToBottom() else: self.layerstack.moveSelectedToTop() ActionInfo = ShortcutManager.ActionInfo rawLayer.shortcutRegistration = ( "i", ActionInfo( "Prediction Layers", "Bring Input To Top/Bottom", "Bring Input To Top/Bottom", toggleTopToBottom, self.viewerControlWidget(), rawLayer ) ) layers.append(rawLayer) # since we start with existing labels, it makes sense to start # with the first one selected. This would make more sense in # __init__(), but it does not take effect there. #self.selectLabel(0) return layers def _disconnect_dataChange_callback(self, colortable_changed_callback, *args ): """ When instances of the prediction layer are garbage collected, we no longer want the list model to call them back. This function disconnects the signal that was connected in setupLayers, above. """ self._labelControlUi.labelListModel.dataChanged.disconnect( colortable_changed_callback ) def _setPredictionColorTable(self, weak_predictLayer, index1, index2): predictLayer = weak_predictLayer() if predictLayer is None: return row = index1.row() self._setPredictionColorTableForRow(predictLayer, row) def _setPredictionColorTableForRow(self, predictLayer, row): if row >= 0 and row < self._labelControlUi.labelListModel.rowCount(): element = self._labelControlUi.labelListModel[row] oldcolor = self._colorTable16_forpmaps[row+1] if oldcolor != element.pmapColor().rgba(): self._colorTable16_forpmaps[row+1] = element.pmapColor().rgba() predictLayer.colorTable = self._colorTable16_forpmaps @staticmethod def _getObject(slot, pos5d): slicing = tuple(slice(i, i+1) for i in pos5d) arr = slot[slicing].wait() return arr.flat[0]
[docs] def onClick(self, layer, pos5d, pos): """Extracts the object index that was clicked on and updates that object's label. """ label = self.editor.brushingModel.drawnNumber if label == self.editor.brushingModel.erasingNumber: label = 0 topLevelOp = self.topLevelOperatorView.viewed_operator() imageIndex = topLevelOp.LabelInputs.index( self.topLevelOperatorView.LabelInputs ) operatorAxisOrder = self.topLevelOperatorView.SegmentationImagesOut.meta.getAxisKeys() assert operatorAxisOrder == list('txyzc'), \ "Need to update onClick() if the operator no longer expects volumina axis order. Operator wants: {}".format( operatorAxisOrder ) self.topLevelOperatorView.assignObjectLabel(imageIndex, pos5d, label)
def handleEditorRightClick(self, position5d, globalWindowCoordinate): layer = self.getLayer('Labels') obj = self._getObject(layer.segmentationImageSlot, position5d) if obj == 0: return menu = QMenu(self) text = "Print info for object {} in the terminal".format(obj) menu.addAction(text) # IPC stuff if ilastik_config.getboolean("ilastik", "debug"): menu.addSeparator() if any(IPCFacade().sending): time = position5d[0] sub = menu.addMenu("Hilite Object") for mode in Protocol.ValidHiliteModes[:-1]: where = Protocol.simple("and", time=time, ilastik_id=obj) cmd = Protocol.cmd(mode, where) sub.addAction(mode.capitalize(), IPCFacade().broadcast(cmd)) menu.addAction("Clear Hilite", IPCFacade().broadcast(Protocol.cmd("clear"))) else: menu.addAction("Open IPC Server Window", IPCFacade().show_info) menu.addAction("Start All IPC Servers", IPCFacade().start) menu.addSeparator() clearlabel = "Clear label for object {}".format(obj) menu.addAction(clearlabel) numLabels = self.labelListData.rowCount() label_actions = [] for l in range(numLabels): color_icon = self.labelListData.createIconForLabel(l) act_text = 'Label object {} as "{}"'.format(obj, self.labelListData[l].name) act = QAction(color_icon, act_text, menu) act.setIconVisibleInMenu(True) label_actions.append(act_text) menu.addAction(act) action = menu.exec_(globalWindowCoordinate) if action is None: return if action.text() == text: numpy.set_printoptions(precision=4) print( "------------------------------------------------------------" ) print( "object: {}".format(obj) ) t = position5d[0] labels = self.op.LabelInputs([t]).wait()[t] if len(labels) > obj: label = int(labels[obj]) else: label = "none" print( "label: {}".format(label) ) print( 'features:' ) feats = self.op.ObjectFeatures([t]).wait()[t] selected = self.op.SelectedFeatures([]).wait() for plugin in sorted(feats.keys()): if plugin == default_features_key or plugin not in selected: continue print( "Feature category: {}".format(plugin) ) for featname in sorted(feats[plugin].keys()): if featname not in selected[plugin]: continue value = feats[plugin][featname] ft = numpy.asarray(value.squeeze())[obj] print( "{}: {}".format(featname, ft) ) if len(selected)>0: pred = 'none' if self.op.Predictions.ready(): preds = self.op.Predictions([t]).wait()[t] if len(preds) >= obj: pred = int(preds[obj]) prob = 'none' if self.op.Probabilities.ready(): probs = self.op.Probabilities([t]).wait()[t] if len(probs) >= obj: prob = probs[obj] print( "probabilities: {}".format(prob) ) print( "prediction: {}".format(pred) ) print( "------------------------------------------------------------" ) elif action.text()==clearlabel: topLevelOp = self.topLevelOperatorView.viewed_operator() imageIndex = topLevelOp.LabelInputs.index( self.topLevelOperatorView.LabelInputs ) self.topLevelOperatorView.assignObjectLabel(imageIndex, position5d, 0) #todo: remove old elif self.applet.connected_to_knime: if action.text()==knime_hilite: data = {'command': 0, 'objectid': 'Row'+str(obj)} self.applet.sendMessageToServer.emit('knime', data) elif action.text()==knime_unhilite: data = {'command': 1, 'objectid': 'Row'+str(obj)} self.applet.sendMessageToServer.emit('knime', data) elif action.text()==knime_clearhilite: data = {'command': 2} self.applet.sendMessageToServer.emit('knime', data) else: try: label = label_actions.index(action.text()) except ValueError: return topLevelOp = self.topLevelOperatorView.viewed_operator() imageIndex = topLevelOp.LabelInputs.index( self.topLevelOperatorView.LabelInputs ) self.topLevelOperatorView.assignObjectLabel(imageIndex, position5d, label+1) def setVisible(self, visible): super(ObjectClassificationGui, self).setVisible(visible) if visible: subslot_index = self.op.current_view_index() if subslot_index == -1: return temp = self.op.triggerTransferLabels(subslot_index) else: temp = None if temp is not None: new_labels, old_labels_lost, new_labels_lost = temp labels_lost = dict(old_labels_lost.items() + new_labels_lost.items()) if sum(len(v) for v in labels_lost.itervalues()) > 0: self.warnLost(labels_lost) @threadRouted def warnLost(self, labels_lost): box = QMessageBox(QMessageBox.Warning, 'Warning', 'Some of your labels could not be transferred', QMessageBox.NoButton, self) messages = { 'full': "These labels were lost completely:", 'partial': "These labels were lost partially:", 'conflict': "These new labels conflicted:" } default_message = "These labels could not be transferred:" _sep = "\t" cases = [] for k, val in labels_lost.iteritems(): if len(val) > 0: msg = messages.get(k, default_message) axis = _sep.join(["X", "Y", "Z"]) coords = "\n".join([_sep.join(["{:<8.1f}".format(i) for i in item]) for item in val]) cases.append("\n".join([msg, axis, coords])) box.setDetailedText("\n\n".join(cases)) self.logBox(box) box.show() @threadRouted
[docs] def handleWarnings(self, *args, **kwargs): """ handle incoming warning messages by opening a pop-up window """ # FIXME: dialog should not steal focus # get warning from operator warning = self.op.Warnings[:].wait() # log the warning message in any case logger.warn(warning['text']) # create dialog only once to prevent a pop-up window cascade if self.badObjectBox is None: box = BadObjectsDialog(warning, self) box.move(self.geometry().width(), 0) self.badObjectBox = box box = self.badObjectBox box.setWindowTitle(warning['title']) box.setText(warning['text']) box.setInformativeText(warning.get('info', '')) box.setDetailedText(warning.get('details', '')) box.show()
@property def gui_applet(self): return self.applet def get_export_dialog_title(self): return "Export Object Information"
class BadObjectsDialog(QMessageBox): def __init__(self, warning, parent): super(BadObjectsDialog, self).__init__(QMessageBox.Warning, warning['title'], warning['text'], QMessageBox.NoButton, parent) self.setWindowModality(Qt.NonModal) # make a button to connect to the logging callback button = QPushButton(parent=self) button.setText("Print Details To Log...") self.addButton(QMessageBox.Close) self.addButton(button, QMessageBox.ActionRole) # do not close the dialog when print is clicked # => remove the 'close' callback button.clicked.disconnect() button.clicked.connect(self._printToLog) def _printToLog(self, *args, **kwargs): parts = [] for s in (self.text(), self.informativeText(), self.detailedText()): if len(s) > 0: parts.append(encode_from_qstring(s)) msg = "\n".join(parts) logger.warn(msg)