Exemple #1
0
    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)])
Exemple #2
0
    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)
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #6
0
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()
Exemple #7
0
 def setTileWidth(self, tileWidth):
     self._tileWidth = tileWidth
     PreferencesManager().set("ImageScene2D", "tileWidth", tileWidth)
Exemple #8
0
    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()
Exemple #9
0
 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)
Exemple #12
0
    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 )
Exemple #13
0
    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))
Exemple #16
0
    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)
Exemple #17
0
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()