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))
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