Пример #1
0
class VolumeEditor(QObject):
    newImageView2DFocus = pyqtSignal()
    shapeChanged = pyqtSignal()

    @property
    def showDebugPatches(self):
        return self._showDebugPatches

    @showDebugPatches.setter
    def showDebugPatches(self, show):
        for s in self.imageScenes:
            s.showTileOutlines = show
        self._showDebugPatches = show

    @property
    def showTileProgress(self):
        return self._showTileProgress

    @showDebugPatches.setter
    def showTileProgress(self, show):
        for s in self.imageScenes:
            s.showTileProgress = show
        self._showTileProgress = show

    @property
    def cacheSize(self):
        return self._cacheSize

    @cacheSize.setter
    def cacheSize(self, cache_size):
        self._cacheSize = cache_size
        for s in self.imageScenes:
            s.setCacheSize(cache_size)

    @property
    def navigationInterpreterType(self):
        return type(self.navInterpret)

    @navigationInterpreterType.setter
    def navigationInterpreterType(self, navInt):
        self.navInterpret = navInt(self.navCtrl)
        self.eventSwitch.interpreter = self.navInterpret

    def setNavigationInterpreter(self, navInterpret):
        self.navInterpret = navInterpret
        self.eventSwitch.interpreter = self.navInterpret

    @property
    def syncAlongAxes(self):
        """Axes orthogonal to slices, whose values are synced between layers.

        Returns: a tuple of up to three values, encoding:
                 0 - time
                 1 - space
                 2 - channel

                 for example the meaning of (0,1) is: time and orthogonal space axes
                 are synced for all layers, channel is not. (For the x-y slice, the space
                 axis would be z and so on.)

        """
        return tuple(self._sync_along)

    @property
    def dataShape(self):
        return self.posModel.shape5D

    @dataShape.setter
    def dataShape(self, s):
        self.cropModel.set_volume_shape_3d_cropped([0, 0, 0], s[1:4])
        self.cropModel.set_time_shape_cropped(0, s[0])

        self.posModel.shape5D = s
        # for 2D images, disable the slice intersection marker
        is_2D = (numpy.asarray(s[1:4]) == 1).any()
        if is_2D:
            self.navCtrl.indicateSliceIntersection = False
        else:
            for i in range(3):
                self.parent.volumeEditorWidget.quadview.ensureMinimized(i)

        self.shapeChanged.emit()

        for i, v in enumerate(self.imageViews):
            v.sliceShape = self.posModel.sliceShape(axis=i)
        self.view3d.set_shape(s[1:4])

    def lastImageViewFocus(self, axis):
        self._lastImageViewFocus = axis
        self.newImageView2DFocus.emit()

    def __init__(
        self, layerStackModel, parent, labelsink=None, crosshair=True, is_3d_widget_visible=False, syncAlongAxes=(0, 1)
    ):
        super(VolumeEditor, self).__init__(parent=parent)
        self._sync_along = tuple(syncAlongAxes)

        ##
        ## properties
        ##
        self._showDebugPatches = False
        self._showTileProgress = True

        ##
        ## base components
        ##
        self._undoStack = QUndoStack()
        self.layerStack = layerStackModel
        self.posModel = PositionModel(self)
        self.brushingModel = BrushingModel()
        self.cropModel = CropExtentsModel(self)

        self.imageScenes = [
            ImageScene2D(self.posModel, (0, 1, 4), swapped_default=True),
            ImageScene2D(self.posModel, (0, 2, 4)),
            ImageScene2D(self.posModel, (0, 3, 4)),
        ]
        self.imageViews = [ImageView2D(parent, self.cropModel, self.imageScenes[i]) for i in [0, 1, 2]]
        self.imageViews[0].focusChanged.connect(lambda arg=0: self.lastImageViewFocus(arg))
        self.imageViews[1].focusChanged.connect(lambda arg=1: self.lastImageViewFocus(arg))
        self.imageViews[2].focusChanged.connect(lambda arg=2: self.lastImageViewFocus(arg))
        self._lastImageViewFocus = 0

        if not crosshair:
            for view in self.imageViews:
                view._crossHairCursor.enabled = False

        self.imagepumps = self._initImagePumps()

        self.view3d = self._initView3d(is_3d_widget_visible)

        names = ["x", "y", "z"]
        for scene, name, pump in zip(self.imageScenes, names, self.imagepumps):
            scene.setObjectName(name)
            scene.stackedImageSources = pump.stackedImageSources

        self.cacheSize = 50

        ##
        ## interaction
        ##

        # navigation control
        self.navCtrl = NavigationController(self.imageViews, self.imagepumps, self.posModel, view3d=self.view3d)
        self.navInterpret = NavigationInterpreter(self.navCtrl)

        # event switch
        self.eventSwitch = EventSwitch(self.imageViews, self.navInterpret)

        # brushing control
        if crosshair:
            self.crosshairController = CrosshairController(self.brushingModel, self.imageViews)
        self.brushingController = BrushingController(
            self.brushingModel, self.posModel, labelsink, undoStack=self._undoStack
        )
        self.brushingInterpreter = BrushingInterpreter(self.navCtrl, self.brushingController)

        for v in self.imageViews:
            self.brushingController._brushingModel.brushSizeChanged.connect(v._sliceIntersectionMarker._set_diameter)

        # thresholding control
        self.thresInterpreter = ThresholdingInterpreter(self.navCtrl, self.layerStack, self.posModel)

        # By default, don't show cropping controls
        self.showCropLines(False)

        ##
        ## connect
        ##
        self.posModel.timeChanged.connect(self.navCtrl.changeTime)
        self.posModel.slicingPositionChanged.connect(self.navCtrl.moveSlicingPosition)
        if crosshair:
            self.posModel.cursorPositionChanged.connect(self.navCtrl.moveCrosshair)
        self.posModel.slicingPositionSettled.connect(self.navCtrl.settleSlicingPosition)

        self.layerStack.layerAdded.connect(self._onLayerAdded)
        self.parent = parent
        self._setUpShortcuts()

    def _reset(self):
        for s in self.imageScenes:
            s.reset()

    def scheduleSlicesRedraw(self):
        for s in self.imageScenes:
            s._invalidateRect()

    def setInteractionMode(self, name):
        modes = {
            "navigation": self.navInterpret,
            "brushing": self.brushingInterpreter,
            "thresholding": self.thresInterpreter,
        }
        self.eventSwitch.interpreter = modes[name]

    def showCropLines(self, visible):
        for view in self.imageViews:
            view.showCropLines(visible)

    def setTileWidth(self, tileWidth):
        for i in self.imageScenes:
            i.setTileWidth(tileWidth)

    def cleanUp(self):
        QApplication.processEvents()
        for scene in self._imageViews:
            scene.close()
            scene.deleteLater()
        self._imageViews = []
        QApplication.processEvents()

    def closeEvent(self, event):
        event.accept()

    def setLabelSink(self, labelsink):
        self.brushingController.setDataSink(labelsink)

    ##
    ## private
    ##
    def _setUpShortcuts(self):
        mgr = ShortcutManager()
        ActionInfo = ShortcutManager.ActionInfo

        def _undo():
            idx = self._undoStack.index()
            self._undoStack.setIndex(idx - 1)

        def _redo():
            cap = self._undoStack.count()
            idx = self._undoStack.index()
            self._undoStack.setIndex(min(cap, idx + 1))

        mgr.register("Ctrl+Z", ActionInfo("Labeling", "Undo", "Undo last action", _undo, self.parent, None))
        mgr.register("Ctrl+Y", ActionInfo("Labeling", "Redo", "Redo last action", _redo, self.parent, None))

    def _initImagePumps(self):
        alongTXC = SliceProjection(abscissa=2, ordinate=3, along=[0, 1, 4])
        alongTYC = SliceProjection(abscissa=1, ordinate=3, along=[0, 2, 4])
        alongTZC = SliceProjection(abscissa=1, ordinate=2, along=[0, 3, 4])

        imagepumps = []
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTXC, self._sync_along))
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTYC, self._sync_along))
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTZC, self._sync_along))

        return imagepumps

    def _initView3d(self, is_3d_widget_visible):
        from .view3d.overview3d import Overview3D

        view3d = Overview3D(is_3d_widget_visible=is_3d_widget_visible)

        def onSliceDragged():
            self.posModel.slicingPos = view3d.get_slice()

        view3d.slice_changed.connect(onSliceDragged)
        return view3d

    def _onLayerAdded(self, layer, row):
        self.navCtrl.layerChangeChannel(layer)
        layer.channelChanged.connect(partial(self.navCtrl.layerChangeChannel, layer=layer))
Пример #2
0
class GridTableView(QTableView):
    BasicTypes = [
        'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Int8', 'Int16', 'Int32',
        'Int64', 'Bool', 'Float', 'Double', 'String'
    ]

    def __init__(self, model, *args):
        super(GridTableView, self).__init__(*args)

        #self.path = path

        self.model = model
        self.setModel(model)

        self.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.verticalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.horizontalHeader().customContextMenuRequested['QPoint'].connect(
            self.OnCustomContextMenuRequestedH)
        self.verticalHeader().customContextMenuRequested['QPoint'].connect(
            self.OnCustomContextMenuRequestedV)
        self.setItemDelegate(TableViewDelegate(self))
        self.verticalHeader().setSectionsMovable(True)

        self.setRowHeight(1, 150)

        self.undoStack = QUndoStack()
        self.undoStackIndex = 0

        self.undoStack.indexChanged.connect(self.OnUndoStackIndexChanged)

        self.setCornerButtonEnabled(False)

    @property
    def Model(self):
        return self.model

    def Paste(self, fileName):
        import xlrd
        workbook = xlrd.open_workbook(fileName)
        if len(workbook.sheet_names()) > 0:

            dialog = QtWidgets.QDialog(self)
            dialog.setWindowTitle("Select Sheet")
            vBoxLayout = QtWidgets.QVBoxLayout(dialog)

            comboBox = QtWidgets.QComboBox(dialog)
            comboBox.addItems(workbook.sheet_names())

            hBoxLayout = QtWidgets.QHBoxLayout()

            nextBtn = QtWidgets.QPushButton(dialog)
            nextBtn.setText('Paste')
            nextBtn.setDefault(True)

            def OnNextBtnClicked(args, dialog=dialog):
                dialog.done(QtWidgets.QDialog.Accepted)
                dialog = None

            nextBtn.clicked.connect(OnNextBtnClicked)

            hBoxLayout.addStretch(2)
            hBoxLayout.addWidget(nextBtn, 1)

            vBoxLayout.addWidget(comboBox)
            vBoxLayout.addLayout(hBoxLayout)

            if dialog.exec_() == QtWidgets.QDialog.Accepted:
                sheet = workbook.sheet_by_name(comboBox.currentText())

                datas = []
                for row in range(sheet.nrows):
                    colData = []
                    for col in range(sheet.ncols):
                        cell = sheet.cell(row, col)
                        if cell.ctype == 3:
                            colData.append(
                                xlrd.xldate.xldate_as_datetime(
                                    cell.value,
                                    1).strftime("%Y-%m-%d %H:%M:%S"))
                        elif cell.ctype == 2:
                            if cell.value % 1 == 0.0:
                                colData.append(str(int(cell.value)))
                            else:
                                colData.append(str(cell.value))
                        else:
                            colData.append(str(cell.value))

                    datas.append(colData)

                if len(datas) <= 0 or len(datas[0]) <= 0:
                    QtWidgets.QMessageBox.critical(self, 'Error',
                                                   'Format error!')
                else:
                    self.PushUndoStack(
                        TableViewPasteExcelCommand(self.model, datas))
            dialog.destroy()

    def Undo(self):
        self.undoStack.undo()

    def Redo(self):
        self.undoStack.redo()

    def PushUndoStack(self, v):
        self.undoStack.push(v)

    def CreateDirs(self, path):

        try:
            paths = os.path.split(path)
            os.makedirs(paths[0])
        except Exception as e:
            pass

    def Save(self):
        if self.undoStackIndex != self.undoStack.index():
            self.undoStackIndex = self.undoStack.index()

            try:
                data = []
                table = []
                for i in range(0, self.model.columnCount()):
                    index0 = self.model.data(self.model.index(0, i))
                    index1 = self.model.data(self.model.index(1, i))
                    o = (i, self.model.headerData(i, Qt.Horizontal), index0,
                         index1)
                    table.append(o)

                for i in range(2, self.model.rowCount()):
                    d = []
                    for j in range(0, self.model.columnCount()):
                        v = self.model.data(self.model.index(i, j))
                        if v == None:
                            d.append('')
                        else:
                            d.append(v)

                    data.append(d)

                return json.dumps(
                    {
                        'data': data,
                        'table': table,
                        'activeColumn': self.model.activeColumn
                    },
                    indent=1,
                    ensure_ascii=False)

            except Exception as e:
                pass

        return None

    def OnUndoStackIndexChanged(self, index):
        gMainWindow = GlobalObjs.GetValue('MainWindow')
        if self.undoStackIndex == index:
            gMainWindow.EnableSave(False, self)
        else:
            gMainWindow.EnableSave(True, self)
        pass

    @property
    def IsChanged(self):
        index = self.undoStack.index()
        return self.undoStackIndex != index

    def OnCustomContextMenuRequestedH(self, pt):
        self.OnCustomContextMenuRequested(pt, self.horizontalHeader())

    def OnCustomContextMenuRequestedV(self, pt):
        self.OnCustomContextMenuRequested(pt, self.verticalHeader())

    def OnAddCol(self):
        inputDialog = InputDialog.InputDialog(self)
        if inputDialog.exec_() == QtWidgets.QDialog.Accepted:
            for i in range(0, self.model.columnCount()):
                if inputDialog.GetTextValue() == self.model.headerData(
                        i, Qt.Horizontal):
                    QtWidgets.QMessageBox.critical(self, 'Error',
                                                   'Duplicate name!')
                    return

            self.PushUndoStack(
                TableViewAddColumnCommand(self.model,
                                          inputDialog.GetTextValue()))

    def OnAddRow(self):
        self.PushUndoStack(TableViewAddRowCommand(1, self.model))
        pass

    def OnAddRowN(self):
        ret = QInputDialog.getInt(self, 'Message', 'Please enter row number:',
                                  1, 1, 1000)
        if ret[1] == True:
            self.PushUndoStack(TableViewAddRowCommand(int(ret[0]), self.model))
        pass

    def OnDelCol(self):
        indexs = self.selectionModel().selectedColumns()
        if indexs != None and len(indexs) > 0:
            cols = []
            for v in indexs:
                cols.append(v.column())
            self.PushUndoStack(TableViewDelColumnCommand(self.model, cols))

    def OnEditCol(self):
        index = self.horizontalHeader().logicalIndexAt(self.sender().data())
        if index != None and index >= 0:
            v = self.model.headerData(index, Qt.Horizontal)
            inputDialog = InputDialog.InputDialog(self, str(v))
            if inputDialog.exec_() == QtWidgets.QDialog.Accepted:
                for i in range(0, self.model.columnCount()):
                    if inputDialog.GetTextValue() == self.model.headerData(
                            i, Qt.Horizontal) and i != index:
                        QtWidgets.QMessageBox.critical(self, 'Error',
                                                       'Duplicate name!')
                        return

                self.PushUndoStack(
                    TableViewEditItemCommand(None, index, v,
                                             inputDialog.GetTextValue(),
                                             self.model, True))

    def OnSetColIndex(self):
        column = self.horizontalHeader().logicalIndexAt(self.sender().data())
        if column != None and column >= 0:
            if self.model.activeColumn != column:
                typename = self.model.data(self.model.index(0, column))
                if typename not in GridTableView.BasicTypes:
                    QtWidgets.QMessageBox.critical(
                        self, 'Error', 'column index must be the basic type!')
                else:
                    self.PushUndoStack(
                        TableViewSetActiveColumnCommand(self.model, column))

    def OnDelRow(self):
        indexs = self.selectionModel().selectedRows()
        if indexs != None and len(indexs) > 0:

            rows = []
            for v in indexs:
                if v.row() != 0 and v.row() != 1:
                    rows.append(v.row())

            self.PushUndoStack(TableViewDelRowCommand(rows, self.model))

    def OnCustomContextMenuRequested(self, pt, header):

        action_AddCol = QtWidgets.QAction("添加列", None, triggered=self.OnAddCol)

        action_DelCol = QtWidgets.QAction("删除列", None, triggered=self.OnDelCol)
        action_EditCol = QtWidgets.QAction("编辑列",
                                           None,
                                           triggered=self.OnEditCol)
        action_SetColIndex = QtWidgets.QAction("设置为索引列",
                                               None,
                                               triggered=self.OnSetColIndex)

        action_AddRow = QtWidgets.QAction("添加行", None, triggered=self.OnAddRow)
        action_AddRowN = QtWidgets.QAction("添加多行",
                                           None,
                                           triggered=self.OnAddRowN)
        action_DelRow = QtWidgets.QAction("删除行", None, triggered=self.OnDelRow)

        action_DelCol.setData(pt)
        action_EditCol.setData(pt)
        action_SetColIndex.setData(pt)

        action_DelRow.setData(pt)

        menu = QtWidgets.QMenu("menuTable")
        menu.addAction(action_AddCol)
        menu.addAction(action_EditCol)
        menu.addAction(action_SetColIndex)
        menu.addAction(action_DelCol)
        menu.addSeparator()
        menu.addAction(action_AddRow)
        menu.addAction(action_AddRowN)
        menu.addAction(action_DelRow)

        if header.orientation() == Qt.Vertical:
            index = header.logicalIndexAt(pt)
            if index == -1 or index == 0 or index == 1:
                action_DelRow.setEnabled(False)

            action_DelCol.setEnabled(False)
            action_EditCol.setEnabled(False)
            action_SetColIndex.setEnabled(False)
        elif header.orientation() == Qt.Horizontal:
            index = header.logicalIndexAt(pt)
            if index == -1:
                action_DelCol.setEnabled(False)
                action_EditCol.setEnabled(False)
                action_SetColIndex.setEnabled(False)

            action_DelRow.setEnabled(False)

        menu.exec_(QtGui.QCursor.pos())
        menu.destroy()

        pass