def addDvidVolume(self, roleIndex, laneIndex): # TODO: Provide list of recently used dvid hosts, loaded from user preferences recent_hosts_pref = PreferencesManager.Setting("DataSelection", "Recent DVID Hosts") recent_hosts = recent_hosts_pref.get() if not recent_hosts: recent_hosts = ["localhost:8000"] recent_hosts = filter(lambda h: h, recent_hosts) from dvidDataSelectionBrowser import DvidDataSelectionBrowser browser = DvidDataSelectionBrowser(recent_hosts, parent=self) if browser.exec_() == DvidDataSelectionBrowser.Rejected: return if None in browser.get_selection(): QMessageBox.critical("Couldn't use your selection.") return rois = None hostname, dset_uuid, volume_name, uuid = browser.get_selection() dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals()) subvolume_roi = browser.get_subvolume_roi() # Relocate host to top of 'recent' list, and limit list to 10 items. try: i = recent_hosts.index(recent_hosts) del recent_hosts[i] except ValueError: pass finally: recent_hosts.insert(0, hostname) recent_hosts = recent_hosts[:10] # Save pref recent_hosts_pref.set(recent_hosts) if subvolume_roi is None: self.addFileNames([dvid_url], roleIndex, laneIndex) else: # In ilastik, we display the dvid volume axes in C-order, despite the dvid convention of F-order # Transpose the subvolume roi to match # (see implementation of OpDvidVolume) start, stop = subvolume_roi start = tuple(reversed(start)) stop = tuple(reversed(stop)) self.addFileNames([dvid_url], roleIndex, laneIndex, [(start, stop)])
def __init__(self, view): """ Constructor. :param view: The parent widget -> ImageView2D """ super(WysiwygExportOptionsDlg, self).__init__(view) uic.loadUi(os.path.splitext(__file__)[0] + '.ui', self) self.view = view # indicators for ok button self._pattern_ok = False self._directory_ok = False self.fileExt = "" # properties self.along = self.view.scene()._along reverse = -1 if self.view.scene().is_swapped else 1 self.inputAxes = ['t', 'x', 'y', 'z', 'c'] self.shape = self.view.scene()._posModel.shape5D self.sliceAxes = [ i for i in range(len(self.inputAxes)) if not i in self.along ][::reverse] self.sliceCoords = ''.join([ a for i, a in enumerate(self.inputAxes) if not i in self.along ])[::reverse] # Init child widgets self._initSubregionWidget() self._initFileOptionsWidget() self._initExportInfoWidget() # disable OK button if file path/pattern are invalid self._updateOkButton() # See self.eventFilter() self.installEventFilter(self) default_location = PreferencesManager().get( "WYSIWYG", "export directory", default=os.path.expanduser("~")) self.directoryEdit.setText(default_location) # hide stack tiffs if Wand is not installed if wand is None: self.stack_tiffs_checkbox.setVisible(False)
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)
def register(self, group, description, shortcut, objectWithToolTip=None): """ Register a shortcut with the shortcut manager. Note: If the new shortcut uses the same key sequence as a shortcut that already exists, the original shortcut is disabled, and this new shortcut takes it's place. group - The GUI category of this shortcut description - A description of the shortcut action (shows up as default tooltip text) shortcut - A QShortcut objectWithToolTip - (optional) If provided, used to update the tooltip text with the shortcut keys. (See ABC above) """ assert description is not None assert objectWithToolTip is None or isinstance(objectWithToolTip, ObjectWithToolTipABC) if not group in self._shortcuts: self._shortcuts[group] = collections.OrderedDict() # If we've got user preferences for this shortcut, apply them now. groupKeys = PreferencesManager().get(self.PreferencesGroup, group) if groupKeys is not None and description in groupKeys: keyseq = groupKeys[description] shortcut.setKey(keyseq) # Purge invalid shortcuts self._purgeDeletedShortcuts() # Before we add this shortcut to our dict, disable any other shortcuts it replaces conflicting_shortcuts = self._findExistingShortcuts( shortcut.key().toString()) for conflicted in conflicting_shortcuts: conflicted.setKey(QKeySequence("")) self.updateToolTip(conflicted) self._shortcuts[group][shortcut] = (description, objectWithToolTip) self.updateToolTip(shortcut)
def import_labeling_layer(labelLayer, labelingSlots, parent_widget=None): """ Prompt the user for layer import settings, and perform the layer import. :param labelLayer: The top label layer source :param labelingSlots: An instance of LabelingGui.LabelingSlots :param parent_widget: The Qt GUI parent object """ writeSeeds = labelingSlots.labelInput assert isinstance( writeSeeds, lazyflow.graph.Slot), "slot is of type %r" % (type(writeSeeds)) opLabels = writeSeeds.getRealOperator() assert isinstance(opLabels, lazyflow.graph.Operator ), "slot's operator is of type %r" % (type(opLabels)) recentlyImported = PreferencesManager().get('labeling', 'recently imported') mostRecentProjectPath = PreferencesManager().get('shell', 'recently opened') mostRecentImageFile = PreferencesManager().get('DataSelection', 'recent image') if recentlyImported: defaultDirectory = os.path.split(recentlyImported)[0] elif mostRecentProjectPath: defaultDirectory = os.path.split(mostRecentProjectPath)[0] elif mostRecentImageFile: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') fileNames = DataSelectionGui.getImageFileNamesToOpen( parent_widget, defaultDirectory) fileNames = map(str, fileNames) if not fileNames: return PreferencesManager().set('labeling', 'recently imported', fileNames[0]) try: # Initialize operators opImport = OpInputDataReader(parent=opLabels.parent) opCache = OpArrayCache(parent=opLabels.parent) opMetadataInjector = OpMetadataInjector(parent=opLabels.parent) opReorderAxes = OpReorderAxes(parent=opLabels.parent) # Set up the pipeline as follows: # # opImport --> opCache --> opMetadataInjector --------> opReorderAxes --(inject via setInSlot)--> labelInput # / / # User-specified axisorder labelInput.meta.axistags opImport.WorkingDirectory.setValue(defaultDirectory) opImport.FilePath.setValue(fileNames[0] if len(fileNames) == 1 else os.path.pathsep.join(fileNames)) assert opImport.Output.ready() opCache.blockShape.setValue(opImport.Output.meta.shape) opCache.Input.connect(opImport.Output) assert opCache.Output.ready() opMetadataInjector.Input.connect(opCache.Output) metadata = opCache.Output.meta.copy() opMetadataInjector.Metadata.setValue(metadata) opReorderAxes.Input.connect(opMetadataInjector.Output) # Transpose the axes for assignment to the labeling operator. opReorderAxes.AxisOrder.setValue(writeSeeds.meta.getAxisKeys()) # We'll show a little window with a busy indicator while the data is loading busy_dlg = QProgressDialog(parent=parent_widget) busy_dlg.setLabelText("Importing Label Data...") busy_dlg.setCancelButton(None) busy_dlg.setMinimum(100) busy_dlg.setMaximum(100) def close_busy_dlg(*args): QApplication.postEvent(busy_dlg, QCloseEvent()) # Load the data from file into our cache # When it's done loading, close the progress dialog. req = opCache.Output[:] req.notify_finished(close_busy_dlg) req.notify_failed(close_busy_dlg) req.submit() busy_dlg.exec_() readData = req.result maxLabels = len(labelingSlots.labelNames.value) # Can't use return_counts feature because that requires numpy >= 1.9 #unique_read_labels, readLabelCounts = numpy.unique(readData, return_counts=True) # This does the same as the above, albeit slower, and probably with more ram. unique_read_labels = numpy.unique(readData) readLabelCounts = numpy.bincount(readData.flat)[unique_read_labels] labelInfo = (maxLabels, (unique_read_labels, readLabelCounts)) del readData # Ask the user how to interpret the data. settingsDlg = LabelImportOptionsDlg(parent_widget, fileNames, opMetadataInjector.Output, labelingSlots.labelInput, labelInfo) def handle_updated_axes(): # The user is specifying a new interpretation of the file's axes updated_axisorder = str(settingsDlg.axesEdit.text()) metadata = opMetadataInjector.Metadata.value.copy() metadata.axistags = vigra.defaultAxistags(updated_axisorder) opMetadataInjector.Metadata.setValue(metadata) settingsDlg.axesEdit.editingFinished.connect(handle_updated_axes) dlg_result = settingsDlg.exec_() if dlg_result != LabelImportOptionsDlg.Accepted: return # Get user's chosen label mapping from dlg labelMapping = settingsDlg.labelMapping # Get user's chosen offsets. # Offsets in dlg only include the file axes, not the 5D axes expected by the label input, # so expand them to full 5D axes_5d = opReorderAxes.Output.meta.getAxisKeys() tagged_offsets = collections.OrderedDict( zip(axes_5d, [0] * len(axes_5d))) tagged_offsets.update( dict( zip(opMetadataInjector.Output.meta.getAxisKeys(), settingsDlg.imageOffsets))) imageOffsets = tagged_offsets.values() # Optimization if mapping is identity if labelMapping.keys() == labelMapping.values(): labelMapping = None # This will be fast (it's already cached) label_data = opReorderAxes.Output[:].wait() # Map input labels to output labels if labelMapping: # There are other ways to do a relabeling (e.g skimage.segmentation.relabel_sequential) # But this supports potentially huge values of unique_read_labels (in the billions), # without needing GB of RAM. mapping_indexes = numpy.searchsorted(unique_read_labels, label_data) new_labels = numpy.array( [labelMapping[x] for x in unique_read_labels]) label_data[:] = new_labels[mapping_indexes] label_roi = numpy.array(roiFromShape(opReorderAxes.Output.meta.shape)) label_roi += imageOffsets label_slice = roiToSlice(*label_roi) writeSeeds[label_slice] = label_data finally: opReorderAxes.cleanUp() opMetadataInjector.cleanUp() opCache.cleanUp() opImport.cleanUp()
def setTileWidth(self, tileWidth): self._tileWidth = tileWidth PreferencesManager().set("ImageScene2D", "tileWidth", tileWidth)
def __init__(self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False): """ * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off * swapped_default -- whether axes should be swapped by default. """ QGraphicsScene.__init__(self, parent=parent) self._along = along self._posModel = posModel # QGraphicsItems can change this if they are in a state that should temporarily forbid brushing # (For example, when the slice intersection marker is in 'draggable' state.) self.allow_brushing = True self._dataShape = (0, 0) self._dataRectItem = None #A QGraphicsRectItem (or None) self._offsetX = 0 self._offsetY = 0 self.name = name self._tileWidth = PreferencesManager().get("ImageScene2D", "tileWidth", default=512) self._stackedImageSources = StackedImageSources(LayerStackModel()) self._showTileOutlines = False # FIXME: We don't show the red 'progress pies' because they look terrible. # If we could fix their timing, maybe it would be worth it. self._showTileProgress = False self._tileProvider = None self._dirtyIndicator = None self._prefetching_enabled = False self._swappedDefault = swapped_default self.reset() # BowWave preemptive caching self.setPreemptiveFetchNumber(preemptive_fetch_number) self._course = (1, 1) # (along, pos or neg direction) self._time = self._posModel.time self._channel = self._posModel.channel self._posModel.timeChanged.connect(self._onTimeChanged) self._posModel.channelChanged.connect(self._onChannelChanged) self._posModel.slicingPositionChanged.connect( self._onSlicingPositionChanged) self._allTilesCompleteEvent = threading.Event() self.dirty = False # We manually keep track of the tile-wise QGraphicsItems that # we've added to the scene in this dict, otherwise we would need # to use O(N) lookups for every tile by calling QGraphicsScene.items() self.tile_graphicsitems = defaultdict( set) # [Tile.id] -> set(QGraphicsItems) self.last_drag_pos = None # See mouseMoveEvent()
def saveSize(): size = self.featureDlg.size() s = (size.width(), size.height()) PreferencesManager().set("featureSelection", "dialog size", s)
def accept(self): PreferencesManager().set("WYSIWYG", "export directory", str(self.directoryEdit.text())) super(WysiwygExportOptionsDlg, self).accept()
def _selectFiles(self): # Find the directory of the most recently opened image file mostRecentStackDirectory = PreferencesManager().get( 'DataSelection', 'recent stack directory') if mostRecentStackDirectory is not None: defaultDirectory = os.path.split(mostRecentStackDirectory)[0] else: defaultDirectory = os.path.expanduser('~') options = QFileDialog.Options(QFileDialog.ShowDirsOnly) if ilastik.config.cfg.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog h5exts = [x.lstrip('.') for x in OpStreamingHdf5SequenceReaderM.H5EXTS] # Launch the "Open File" dialog extensions = vigra.impex.listExtensions().split() extensions.extend(h5exts) filt = "Image files (" + ' '.join('*.' + x for x in extensions) + ')' options = QFileDialog.Options() if ilastik.config.cfg.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog fileNames = QFileDialog.getOpenFileNames(self, "Select Images for Stack", defaultDirectory, filt, options=options) fileNames = map(encode_from_qstring, fileNames) msg = '' if len(fileNames) == 0: return if len(fileNames) == 1: msg += 'Cannot create stack: You only chose a single file. ' msg += 'If your stack is contained in a single file (e.g. a multi-page tiff or ' msg += 'hdf5 volume), please use the "Add File" button.' QMessageBox.warning(self, "Invalid selection", msg) return None pathComponents = PathComponents(fileNames[0]) directory = pathComponents.externalPath PreferencesManager().set('DataSelection', 'recent stack directory', directory) if pathComponents.extension in OpStreamingHdf5SequenceReaderM.H5EXTS: # check for internal paths! internal_paths = self._findCommonInternal(fileNames) if len(internal_paths) == 0: msg += 'Could not find a unique common internal path in' msg += directory + '\n' QMessageBox.warning(self, "Invalid selection", msg) return None elif len(internal_paths) == 1: fileNames = [ '{}/{}'.format(fn, internal_paths[0]) for fn in fileNames ] else: # Ask the user which dataset to choose dlg = H5VolumeSelectionDlg(internal_paths, self) if dlg.exec_() == QDialog.Accepted: selected_index = dlg.combo.currentIndex() selected_dataset = str(internal_paths[selected_index]) fileNames = [ '{}/{}'.format(fn, selected_dataset) for fn in fileNames ] else: msg = 'No valid internal path selected.' QMessageBox.warning(self, "Invalid selection", msg) return None self._updateFileList(fileNames)
def _initLabelUic(self, drawerUiPath): _labelControlUi = uic.loadUi(drawerUiPath) # We own the applet bar ui self._labelControlUi = _labelControlUi # Initialize the label list model model = LabelListModel() _labelControlUi.labelListView.setModel(model) _labelControlUi.labelListModel=model _labelControlUi.labelListModel.rowsRemoved.connect(self._onLabelRemoved) _labelControlUi.labelListModel.elementSelected.connect(self._onLabelSelected) # Connect Applet GUI to our event handlers if hasattr(_labelControlUi, "AddLabelButton"): _labelControlUi.AddLabelButton.setIcon( QIcon(ilastikIcons.AddSel) ) _labelControlUi.AddLabelButton.clicked.connect( bind(self._addNewLabel) ) _labelControlUi.labelListModel.dataChanged.connect(self.onLabelListDataChanged) # Initialize the arrow tool button with an icon and handler iconPath = os.path.split(__file__)[0] + "/icons/arrow.png" arrowIcon = QIcon(iconPath) _labelControlUi.arrowToolButton.setIcon(arrowIcon) _labelControlUi.arrowToolButton.setCheckable(True) _labelControlUi.arrowToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Navigation) ) # Initialize the paint tool button with an icon and handler paintBrushIconPath = os.path.split(__file__)[0] + "/icons/paintbrush.png" paintBrushIcon = QIcon(paintBrushIconPath) _labelControlUi.paintToolButton.setIcon(paintBrushIcon) _labelControlUi.paintToolButton.setCheckable(True) _labelControlUi.paintToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Paint) ) # Initialize the erase tool button with an icon and handler eraserIconPath = os.path.split(__file__)[0] + "/icons/eraser.png" eraserIcon = QIcon(eraserIconPath) _labelControlUi.eraserToolButton.setIcon(eraserIcon) _labelControlUi.eraserToolButton.setCheckable(True) _labelControlUi.eraserToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Erase) ) # Initialize the thresholding tool if hasattr(_labelControlUi, "thresToolButton"): thresholdIconPath = os.path.split(__file__)[0] \ + "/icons/threshold.png" thresholdIcon = QIcon(thresholdIconPath) _labelControlUi.thresToolButton.setIcon(thresholdIcon) _labelControlUi.thresToolButton.setCheckable(True) _labelControlUi.thresToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Threshold) ) # This maps tool types to the buttons that enable them if hasattr(_labelControlUi, "thresToolButton"): self.toolButtons = { Tool.Navigation : _labelControlUi.arrowToolButton, Tool.Paint : _labelControlUi.paintToolButton, Tool.Erase : _labelControlUi.eraserToolButton, Tool.Threshold : _labelControlUi.thresToolButton} else: self.toolButtons = { Tool.Navigation : _labelControlUi.arrowToolButton, Tool.Paint : _labelControlUi.paintToolButton, Tool.Erase : _labelControlUi.eraserToolButton} self.brushSizes = [ 1, 3, 5, 7, 11, 23, 31, 61 ] for size in self.brushSizes: _labelControlUi.brushSizeComboBox.addItem( str(size) ) _labelControlUi.brushSizeComboBox.currentIndexChanged.connect(self._onBrushSizeChange) self.paintBrushSizeIndex = PreferencesManager().get( 'labeling', 'paint brush size', default=0 ) self.eraserSizeIndex = PreferencesManager().get( 'labeling', 'eraser brush size', default=4 )
def _selectFiles(self): # Find the directory of the most recently opened image file mostRecentStackDirectory = PreferencesManager().get( 'DataSelection', 'recent stack directory') if mostRecentStackDirectory is not None: defaultDirectory = os.path.split(mostRecentStackDirectory)[0] else: defaultDirectory = os.path.expanduser('~') options = QFileDialog.Options(QFileDialog.ShowDirsOnly) if ilastik.config.cfg.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog h5exts = [x.lstrip('.') for x in OpStreamingH5N5SequenceReaderM.H5EXTS] # Launch the "Open File" dialog extensions = vigra.impex.listExtensions().split() extensions.extend(h5exts) extensions.extend(OpInputDataReader.n5Selection) filt = "Image files (" + ' '.join('*.' + x for x in extensions) + ')' options = QFileDialog.Options() if ilastik.config.cfg.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog fileNames, _filter = QFileDialog.getOpenFileNames( self, "Select Images for Stack", defaultDirectory, filt, options=options) # For the n5 extension, the attributes.json file has to be selected in the file dialog. # However we need just the n5 directory-file. for i in range(len(fileNames)): if os.path.join("n5", "attributes.json") in fileNames[i]: fileNames[i] = fileNames[i].replace( os.path.sep + "attributes.json", "") msg = '' if len(fileNames) == 0: return pathComponents = PathComponents(fileNames[0]) if (len(fileNames) == 1) and pathComponents.extension not in OpStreamingH5N5SequenceReaderM.H5EXTS \ + OpStreamingH5N5SequenceReaderM.N5EXTS: msg += 'Cannot create stack: You only chose a single file. ' msg += 'If your stack is contained in a single file (e.g. a multi-page tiff) ' msg += 'please use the "Add File" button.' QMessageBox.warning(self, "Invalid selection", msg) return None directory = pathComponents.externalPath PreferencesManager().set('DataSelection', 'recent stack directory', directory) if pathComponents.extension in OpStreamingH5N5SequenceReaderM.H5EXTS or \ pathComponents.extension in OpStreamingH5N5SequenceReaderM.N5EXTS: if len(fileNames) == 1: # open the dialog for globbing: file_name = fileNames[0] dlg = H5N5StackingDlg( parent=self, list_of_paths=self._findInternalStacks(file_name)) if dlg.exec_() == QDialog.Accepted: globstring = '{}/{}'.format(file_name, dlg.get_globstring()) self.patternEdit.setText(globstring) self._applyPattern() return None else: return None else: # check for internal paths internal_paths = self._h5N5FindCommonInternal(fileNames) if len(internal_paths) == 0: msg += 'Could not find a unique common internal path in' msg += directory + '\n' QMessageBox.warning(self, "Invalid selection", msg) return None elif len(internal_paths) == 1: fileNames = [ '{}/{}'.format(fn, internal_paths[0]) for fn in fileNames ] else: # Ask the user which dataset to choose dlg = H5N5VolumeSelectionDlg(internal_paths, self) if dlg.exec_() == QDialog.Accepted: selected_index = dlg.combo.currentIndex() selected_dataset = str(internal_paths[selected_index]) fileNames = [ '{}/{}'.format(fn, selected_dataset) for fn in fileNames ] else: msg = 'No valid internal path selected.' QMessageBox.warning(self, "Invalid selection", msg) return None self._updateFileList(fileNames)
def get_settings_and_export_layer(layer, parent_widget=None): """ Prompt the user for layer export settings, and perform the layer export. """ sourceTags = [True for l in layer.datasources] for i, source in enumerate(layer.datasources): if not hasattr(source, "dataSlot"): sourceTags[i] = False if not any(sourceTags): raise RuntimeError( "can not export from a non-lazyflow data source (layer=%r, datasource=%r)" % (type(layer), type(layer.datasources[0]))) if not _has_lazyflow: raise RuntimeError("lazyflow not installed") import lazyflow dataSlots = [ slot.dataSlot for (slot, isSlot) in zip(layer.datasources, sourceTags) if isSlot is True ] opStackChannels = lazyflow.operators.OpMultiArrayStacker( dataSlots[0].getRealOperator().parent) for slot in dataSlots: assert isinstance( slot, lazyflow.graph.Slot), "slot is of type %r" % (type(slot)) assert isinstance( slot.getRealOperator(), lazyflow.graph.Operator), "slot's operator is of type %r" % (type( slot.getRealOperator())) opStackChannels.AxisFlag.setValue("c") opStackChannels.Images.resize(len(dataSlots)) for i, islot in enumerate(opStackChannels.Images): islot.connect(dataSlots[i]) export_dir = PreferencesManager().get("layer", "export-dir", default=os.path.expanduser("~")) # Create an operator to do the work from lazyflow.operators.ioOperators import OpFormattedDataExport opExport = OpFormattedDataExport(parent=opStackChannels.parent) opExport.OutputFilenameFormat.setValue(os.path.join( export_dir, layer.name)) opExport.Input.connect(opStackChannels.Output) opExport.TransactionSlot.setValue(True) # Use this dialog to populate the operator's slot settings settingsDlg = DataExportOptionsDlg(parent_widget, opExport) # If user didn't cancel, run the export now. if (settingsDlg.exec_() == DataExportOptionsDlg.Accepted): export_dir = PathComponents( opExport.ExportPath.value).externalDirectory PreferencesManager().set("layer", "export-dir", export_dir) helper = ExportHelper(parent_widget) helper.run(opExport) # Clean up our temporary operators opExport.cleanUp() opStackChannels.cleanUp()
def __init__(self, parentApplet, topLevelOperatorView): """ """ super(VigraWatershedViewerGui, self).__init__(parentApplet, topLevelOperatorView) self.topLevelOperatorView = topLevelOperatorView op = self.topLevelOperatorView op.FreezeCache.setValue(True) op.OverrideLabels.setValue({0: (0, 0, 0, 0)}) # Default settings (will be overwritten by serializer) op.InputChannelIndexes.setValue([]) op.SeedThresholdValue.setValue(0.0) op.MinSeedSize.setValue(0) # Init padding gui updates blockPadding = PreferencesManager().get('vigra watershed viewer', 'block padding', 10) op.WatershedPadding.notifyDirty(self.updatePaddingGui) op.WatershedPadding.setValue(blockPadding) self.updatePaddingGui() # Init block shape gui updates cacheBlockShape = PreferencesManager().get('vigra watershed viewer', 'cache block shape', (256, 10)) op.CacheBlockShape.notifyDirty(self.updateCacheBlockGui) op.CacheBlockShape.setValue(tuple(cacheBlockShape)) self.updateCacheBlockGui() # Init seeds gui updates op.SeedThresholdValue.notifyDirty(self.updateSeedGui) op.SeedThresholdValue.notifyReady(self.updateSeedGui) op.SeedThresholdValue.notifyUnready(self.updateSeedGui) op.MinSeedSize.notifyDirty(self.updateSeedGui) self.updateSeedGui() # Init input channel gui updates op.InputChannelIndexes.notifyDirty(self.updateInputChannelGui) op.InputChannelIndexes.setValue([0]) op.InputImage.notifyMetaChanged(bind(self.updateInputChannelGui)) self.updateInputChannelGui() self.thunkEventHandler = ThunkEventHandler(self) # Remember to unsubscribe during shutdown self.__cleanup_fns = [] self.__cleanup_fns.append( partial(op.WatershedPadding.unregisterDirty, self.updatePaddingGui)) self.__cleanup_fns.append( partial(op.CacheBlockShape.unregisterDirty, self.updateCacheBlockGui)) self.__cleanup_fns.append( partial(op.SeedThresholdValue.unregisterDirty, self.updateSeedGui)) self.__cleanup_fns.append( partial(op.SeedThresholdValue.unregisterReady, self.updateSeedGui)) self.__cleanup_fns.append( partial(op.SeedThresholdValue.unregisterUnready, self.updateSeedGui)) self.__cleanup_fns.append( partial(op.MinSeedSize.unregisterDirty, self.updateSeedGui)) self.__cleanup_fns.append( partial(op.InputChannelIndexes.unregisterDirty, self.updateInputChannelGui)) self.__cleanup_fns.append( partial(op.InputImage.unregisterDirty, self.updateInputChannelGui))
def _initLabelUic(self, drawerUiPath): _labelControlUi = uic.loadUi(drawerUiPath) # We own the applet bar ui self._labelControlUi = _labelControlUi # Initialize the label list model model = LabelListModel() _labelControlUi.labelListView.setModel(model) _labelControlUi.labelListModel = model _labelControlUi.labelListModel.rowsRemoved.connect( self._onLabelRemoved) _labelControlUi.labelListModel.elementSelected.connect( self._onLabelSelected) def handleClearRequested(row, name): selection = QMessageBox.warning( self, "Clear labels?", "All '{}' brush strokes will be erased. Are you sure?".format( name), QMessageBox.Ok | QMessageBox.Cancel) if selection != QMessageBox.Ok: return # This only works if the top-level operator has a 'clearLabel' function. self.topLevelOperatorView.clearLabel(row + 1) _labelControlUi.labelListView.clearRequested.connect( handleClearRequested) def handleLabelMergeRequested(from_row, from_name, into_row, into_name): from_label = from_row + 1 into_label = into_row + 1 selection = QMessageBox.warning( self, "Merge labels?", "All '{}' brush strokes will be converted to '{}'. Are you sure?" .format(from_name, into_name), QMessageBox.Ok | QMessageBox.Cancel) if selection != QMessageBox.Ok: return # This only works if the top-level operator has a 'mergeLabels' function. self.topLevelOperatorView.mergeLabels(from_label, into_label) names = list(self._labelingSlots.labelNames.value) names.pop(from_label - 1) self._labelingSlots.labelNames.setValue(names) _labelControlUi.labelListView.mergeRequested.connect( handleLabelMergeRequested) # Connect Applet GUI to our event handlers if hasattr(_labelControlUi, "AddLabelButton"): _labelControlUi.AddLabelButton.setIcon(QIcon(ilastikIcons.AddSel)) _labelControlUi.AddLabelButton.clicked.connect( bind(self._addNewLabel)) _labelControlUi.labelListModel.dataChanged.connect( self.onLabelListDataChanged) # Initialize the arrow tool button with an icon and handler iconPath = os.path.split(__file__)[0] + "/icons/arrow.png" arrowIcon = QIcon(iconPath) _labelControlUi.arrowToolButton.setIcon(arrowIcon) _labelControlUi.arrowToolButton.setCheckable(True) _labelControlUi.arrowToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked( checked, Tool.Navigation)) # Initialize the paint tool button with an icon and handler paintBrushIconPath = os.path.split( __file__)[0] + "/icons/paintbrush.png" paintBrushIcon = QIcon(paintBrushIconPath) _labelControlUi.paintToolButton.setIcon(paintBrushIcon) _labelControlUi.paintToolButton.setCheckable(True) _labelControlUi.paintToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Paint)) # Initialize the erase tool button with an icon and handler eraserIconPath = os.path.split(__file__)[0] + "/icons/eraser.png" eraserIcon = QIcon(eraserIconPath) _labelControlUi.eraserToolButton.setIcon(eraserIcon) _labelControlUi.eraserToolButton.setCheckable(True) _labelControlUi.eraserToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked(checked, Tool.Erase)) # Initialize the thresholding tool if hasattr(_labelControlUi, "thresToolButton"): thresholdIconPath = os.path.split(__file__)[0] \ + "/icons/threshold.png" thresholdIcon = QIcon(thresholdIconPath) _labelControlUi.thresToolButton.setIcon(thresholdIcon) _labelControlUi.thresToolButton.setCheckable(True) _labelControlUi.thresToolButton.clicked.connect( lambda checked: self._handleToolButtonClicked( checked, Tool.Threshold)) # This maps tool types to the buttons that enable them if hasattr(_labelControlUi, "thresToolButton"): self.toolButtons = { Tool.Navigation: _labelControlUi.arrowToolButton, Tool.Paint: _labelControlUi.paintToolButton, Tool.Erase: _labelControlUi.eraserToolButton, Tool.Threshold: _labelControlUi.thresToolButton } else: self.toolButtons = { Tool.Navigation: _labelControlUi.arrowToolButton, Tool.Paint: _labelControlUi.paintToolButton, Tool.Erase: _labelControlUi.eraserToolButton } self.brushSizes = [1, 3, 5, 7, 11, 23, 31, 61] for size in self.brushSizes: _labelControlUi.brushSizeComboBox.addItem(str(size)) _labelControlUi.brushSizeComboBox.currentIndexChanged.connect( self._onBrushSizeChange) self.paintBrushSizeIndex = PreferencesManager().get('labeling', 'paint brush size', default=0) self.eraserSizeIndex = PreferencesManager().get('labeling', 'eraser brush size', default=4)
def import_labeling_layer(labelLayer, labelingSlots, parent_widget=None): """ Prompt the user for layer import settings, and perform the layer import. :param labelLayer: The top label layer source :param labelingSlots: An instance of LabelingGui.LabelingSlots :param parent_widget: The Qt GUI parent object """ writeSeeds = labelingSlots.labelInput assert isinstance( writeSeeds, lazyflow.graph.Slot), "slot is of type %r" % (type(writeSeeds)) opLabels = writeSeeds.getRealOperator() assert isinstance(opLabels, lazyflow.graph.Operator ), "slot's operator is of type %r" % (type(opLabels)) recentlyImported = PreferencesManager().get('labeling', 'recently imported') mostRecentProjectPath = PreferencesManager().get('shell', 'recently opened') mostRecentImageFile = PreferencesManager().get('DataSelection', 'recent image') if recentlyImported: defaultDirectory = os.path.split(recentlyImported)[0] elif mostRecentProjectPath: defaultDirectory = os.path.split(mostRecentProjectPath)[0] elif mostRecentImageFile: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') fileNames = DataSelectionGui.getImageFileNamesToOpen( parent_widget, defaultDirectory) fileNames = map(str, fileNames) if not fileNames: return PreferencesManager().set('labeling', 'recently imported', fileNames[0]) try: # Initialize operators opImport = OpInputDataReader(parent=opLabels.parent) opCache = OpBlockedArrayCache(parent=opLabels.parent) opMetadataInjector = OpMetadataInjector(parent=opLabels.parent) opReorderAxes = OpReorderAxes(parent=opLabels.parent) # Set up the pipeline as follows: # # opImport --> (opCache) --> opMetadataInjector --------> opReorderAxes --(inject via setInSlot)--> labelInput # / / # User-specified axisorder labelInput.meta.axistags opImport.WorkingDirectory.setValue(defaultDirectory) opImport.FilePath.setValue(fileNames[0] if len(fileNames) == 1 else os.path.pathsep.join(fileNames)) assert opImport.Output.ready() maxLabels = len(labelingSlots.labelNames.value) # We don't bother with counting the label pixels # (and caching the data) if it's big (1 GB) if numpy.prod(opImport.Output.meta.shape) > 1e9: reading_slot = opImport.Output # For huge data, we don't go through and search for the pixel values, # because that takes an annoyingly long amount of time. # Instead, we make the reasonable assumption that the input labels are already 1,2,3..N # and we don't tell the user what the label pixel counts are. unique_read_labels = numpy.array(range(maxLabels + 1)) readLabelCounts = numpy.array([-1] * (maxLabels + 1)) labelInfo = (maxLabels, (unique_read_labels, readLabelCounts)) else: opCache.Input.connect(opImport.Output) opCache.CompressionEnabled.setValue(True) assert opCache.Output.ready() reading_slot = opCache.Output # We'll show a little window with a busy indicator while the data is loading busy_dlg = QProgressDialog(parent=parent_widget) busy_dlg.setLabelText("Scanning Label Data...") busy_dlg.setCancelButton(None) busy_dlg.setMinimum(100) busy_dlg.setMaximum(100) def close_busy_dlg(*args): QApplication.postEvent(busy_dlg, QCloseEvent()) # Load the data from file into our cache # When it's done loading, close the progress dialog. req = reading_slot[:] req.notify_finished(close_busy_dlg) req.notify_failed(close_busy_dlg) req.submit() busy_dlg.exec_() readData = req.result # Can't use return_counts feature because that requires numpy >= 1.9 #unique_read_labels, readLabelCounts = numpy.unique(readData, return_counts=True) # This does the same as the above, albeit slower, and probably with more ram. bincounts = chunked_bincount(readData) unique_read_labels = bincounts.nonzero()[0].astype(readData.dtype, copy=False) readLabelCounts = bincounts[unique_read_labels] labelInfo = (maxLabels, (unique_read_labels, readLabelCounts)) del readData opMetadataInjector.Input.connect(reading_slot) metadata = reading_slot.meta.copy() opMetadataInjector.Metadata.setValue(metadata) opReorderAxes.Input.connect(opMetadataInjector.Output) # Transpose the axes for assignment to the labeling operator. opReorderAxes.AxisOrder.setValue(writeSeeds.meta.getAxisKeys()) # Ask the user how to interpret the data. settingsDlg = LabelImportOptionsDlg(parent_widget, fileNames, opMetadataInjector.Output, labelingSlots.labelInput, labelInfo) def handle_updated_axes(): # The user is specifying a new interpretation of the file's axes updated_axisorder = str(settingsDlg.axesEdit.text()) metadata = opMetadataInjector.Metadata.value.copy() metadata.axistags = vigra.defaultAxistags(updated_axisorder) opMetadataInjector.Metadata.setValue(metadata) if opReorderAxes._invalid_axes: settingsDlg.buttonBox.button( QDialogButtonBox.Ok).setEnabled(False) # Red background settingsDlg.axesEdit.setStyleSheet( "QLineEdit { background: rgb(255, 128, 128);" "selection-background-color: rgb(128, 128, 255); }") settingsDlg.axesEdit.editingFinished.connect(handle_updated_axes) # Initialize handle_updated_axes() dlg_result = settingsDlg.exec_() if dlg_result != LabelImportOptionsDlg.Accepted: return # Get user's chosen label mapping from dlg labelMapping = settingsDlg.labelMapping # Get user's chosen offsets, ordered by the 'write seeds' slot axes_5d = opReorderAxes.Output.meta.getAxisKeys() tagged_offsets = collections.OrderedDict( zip(axes_5d, [0] * len(axes_5d))) tagged_offsets.update( dict( zip(opReorderAxes.Output.meta.getAxisKeys(), settingsDlg.imageOffsets))) imageOffsets = tagged_offsets.values() # Optimization if mapping is identity if labelMapping.keys() == labelMapping.values(): labelMapping = None # If the data was already cached, this will be fast. label_data = opReorderAxes.Output[:].wait() # Map input labels to output labels if labelMapping: # There are other ways to do a relabeling (e.g skimage.segmentation.relabel_sequential) # But this supports potentially huge values of unique_read_labels (in the billions), # without needing GB of RAM. mapping_indexes = numpy.searchsorted(unique_read_labels, label_data) new_labels = numpy.array( [labelMapping[x] for x in unique_read_labels]) label_data[:] = new_labels[mapping_indexes] label_roi = numpy.array(roiFromShape(opReorderAxes.Output.meta.shape)) label_roi += imageOffsets label_slice = roiToSlice(*label_roi) writeSeeds[label_slice] = label_data finally: opReorderAxes.cleanUp() opMetadataInjector.cleanUp() opCache.cleanUp() opImport.cleanUp()