''' Created on 15/06/2018 @author: rjag008 ''' from digitiser.marker import PaintGraphicsEllipseItem from digitiser.dataset import PaintDatasetItem from digitiser.view import PaintGraphicsView from digitiser.model import PaintModel from digitiser.scene import PaintGraphicsScene import sys from os import path from collections import OrderedDict dir_path = path.dirname(path.realpath(sys.argv[0])) if not hasattr(sys, 'frozen'): #For py2exe dir_path = path.join(dir_path,"..") uiFile = path.join(dir_path,"./digitiser/PaintUI.ui") try: from PySide import QtCore, QtGui from pysideuiutils.uic import loadUi class PainterWidgetBase(QtGui.QWidget): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) loadUi(uiFile, self) except ImportError: #from PyQt4 import QtCore, QtGui, uic ''' form,base = uic.loadUiType(uiFile) class PainterWidgetBase(base,form): def __init__(self,parent=None): super(base,self).__init__(parent) self.setupUi(self) ''' pass class PaintView(object): meshCoordinateScalingFactor = 250 def __init__(self, model, graphicScene, listDatasets): self._model = model self._graphicScene = graphicScene self._listDatasets = listDatasets self.PenColors = [QtGui.QColor(col) for col in [ QtCore.Qt.red, QtCore.Qt.green, QtCore.Qt.blue,\ QtCore.Qt.cyan, QtCore.Qt.magenta, QtCore.Qt.yellow]] self.datasetColors = self._model.datasetColors if len(self.datasetColors)==0: self.datasetColors[0] = self.createPenAndMaterial(self.PenColors[0]) #Default items color self.reset() self._model.modelChanged.connect(self.modelChanged) self.modelChanged() self._listDatasets.itemChanged.connect(self.updateDatasetName) self._listDatasets.itemDoubleClicked.connect(self.changeItemColor) self._graphicsPaths = {} self.meshSilhouette = None def changeItemColor(self,item): newColor = item.changeColor() if not newColor is None: ds = item._key #Change the color of related path and points self.datasetColors[ds] = self.createPenAndMaterial(newColor) for k in self._pointItems: item, dataset = self._pointItems[k] if dataset==ds: item.setColor(newColor) self.updatePath(ds) def setModel(self,model): self._model = model self.datasetColors = self._model.datasetColors def createPenAndMaterial(self,color): brush = QtGui.QBrush() bcolor = QtGui.QColor(color) bcolor.setAlpha(127) brush.setColor(bcolor) brush.setStyle(QtCore.Qt.CrossPattern) pen = QtGui.QPen(color, 1, QtCore.Qt.SolidLine,QtCore.Qt.FlatCap, QtCore.Qt.MiterJoin) return [pen,brush] def reset(self): self._graphicScene.clear() self._listDatasets.clear() background_file = self._model.getBackgroundFile() if background_file!='Default': pixmap = QtGui.QPixmap(background_file) self._background = self._graphicScene.addPixmap(pixmap) else: self._graphicScene.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern)) self._background = None self._pointItems = {} self._datasetItems = {} self._graphicsPaths = {} self.meshSilhouette = None def setMeshBackground(self,meshmapper,axialElements): ''' Create a background based on the mesh ''' leftwall,rightwall,ladder = meshmapper.getBoundaryPoints() leftwall *=self.meshCoordinateScalingFactor rightwall *=self.meshCoordinateScalingFactor ladder *=self.meshCoordinateScalingFactor #Create lines to represent mesh #Create the brush color = QtGui.QColor(QtCore.Qt.black) pen = QtGui.QPen(color, 5, QtCore.Qt.SolidLine,QtCore.Qt.FlatCap, QtCore.Qt.MiterJoin) if hasattr(self, 'meshSilhouette'): if not self.meshSilhouette is None: self._graphicScene.removeItem(self.meshSilhouette) gpath = QtGui.QPainterPath() #First create points lpts = [] rpts = [] for i in range(axialElements+1): p1 = QtCore.QPointF(ladder[i,0], ladder[i,1]) p2 = QtCore.QPointF(ladder[i,3], ladder[i,4]) lpts.append(p1) rpts.append(p2) #Draw the ladder for i in range(axialElements+1): gpath.moveTo(lpts[i]) gpath.quadTo(lpts[i],rpts[i]) #Left wall lpts = [] for coord in leftwall: p1 = QtCore.QPointF(coord[0],coord[1]) lpts.append(p1) gpath.moveTo(lpts[0]) for i in range(1,len(lpts)): gpath.quadTo(lpts[i-1], lpts[i]) #Interpolate of right rrpts = [] for coord in rightwall: p1 = QtCore.QPointF(coord[0],coord[1]) rrpts.append(p1) gpath.moveTo(rrpts[0]) for i in range(1,len(rrpts)): gpath.quadTo(rrpts[i-1], rrpts[i]) gitem = self._graphicScene.addPath(gpath,pen) gitem.setEnabled(False) gitem.setZValue(0) self.meshSilhouette = gitem self._model.setToScaffoldMeshScaling(self.meshCoordinateScalingFactor) def updateBackground(self): background_file = self._model.getBackgroundFile() if background_file != 'Default': pixmap = QtGui.QPixmap(background_file) zValue = 0 if not self._background is None: zValue = self._background.zValue() self._graphicScene.removeItem(self._background) if hasattr(self, 'meshSilhouette'): if not self.meshSilhouette is None: self._graphicScene.removeItem(self.meshSilhouette) self.meshSilhouette = None self._background = self._graphicScene.addPixmap(pixmap) self._background.setZValue(zValue) def modelChanged(self): self.updatePointItems() self.updateListDatasets() def updatePointItems(self): model_points = self._model.getPoints() set_model_keys = set(model_points.keys()) set_view_keys = set(self._pointItems.keys()) set_intersect_keys = set_model_keys.intersection(set_view_keys) # Added points added_keys = set_model_keys - set_intersect_keys for key in added_keys: pos, dataset = model_points[key] pos = QtCore.QPointF(pos[0], pos[1]) self.addPointItem(key, pos, dataset) # Removed points removed_keys = set_view_keys - set_intersect_keys for key in removed_keys: self.removePointItem(key) # Potentially moved points for key in set_intersect_keys: model_pos = model_points[key][0] model_pos = QtCore.QPointF(model_pos[0], model_pos[1]) item = self._pointItems[key][0] dist = (model_pos - item.scenePos()).manhattanLength() if dist > 0.001: item.setPos(model_pos) def addPointItem(self, key, pos, dataset): if dataset in self.datasetColors: pen = self.datasetColors[dataset][0] else: pen,brush = self.createPenAndMaterial(self.PenColors[dataset % len(self.PenColors)]) self.datasetColors[dataset] = [pen,brush] item = PaintGraphicsEllipseItem(pen, key) item.setZValue(2) self._pointItems[key] = (item, dataset) self._graphicScene.addItem(item) item.setPos(pos) def removePointItem(self, key): item, _ = self._pointItems[key] self._graphicScene.removeItem(item) del self._pointItems[key] def listSelectionChanged(self,dataset): for k in self._pointItems: item,ds = self._pointItems[k] item.setEnabled(ds==dataset) def updateDatasetName(self,item): key = item._key if item._name != str(item.text()): item._name = str(item.text()) self._model.changeDatasetName(key,str(item.text())) else: self.listItemVisibilityToggled(item) def updateListDatasets(self): model_datasets = self._model.getDatasets() set_model_keys = set(model_datasets.keys()) set_view_keys = set(self._datasetItems.keys()) set_intersect_keys = set_model_keys.intersection(set_view_keys) #Set the names to match for common keys def iterAllItems(self): for i in range(self.count()): yield self.item(i) for item in iterAllItems(self._listDatasets): if item._key in set_intersect_keys: name = model_datasets[item._key] item._name = name item.setText(name) # Added datasets added_keys = set_model_keys - set_intersect_keys for key in added_keys: name = model_datasets[key] self.addListItem(key, name) # Removed datasets removed_keys = set_view_keys - set_intersect_keys for key in removed_keys: self.removeListItem(key) #Set current item key = self._model.getCurrentDataset() if not key is None: item = self._datasetItems[key] self._listDatasets.setCurrentItem(item) def addListItem(self, key, name): if key in self.datasetColors: pen = self.datasetColors[key][0] else: pen,brush = self.createPenAndMaterial(self.PenColors[key % len(self.PenColors)]) self.datasetColors[key] = [pen,brush] item = PaintDatasetItem(key, name, pen.color()) self._datasetItems[key] = item self._listDatasets.addItem(item) def listItemVisibilityToggled(self,item): state = item.checkState() key = item._key if self.hasPath(key): self._graphicsPaths[key].setVisible(state) for k in self._pointItems: item,ds = self._pointItems[k] if ds==key: item.setVisible(state) def removeListItem(self, key): # TODO: Iterate through items to find the one with the key def iterAllItems(self): for i in range(self.count()): yield self.item(i) for item in iterAllItems(self._listDatasets): if item._key == key: del item break if self.hasPath(key): self._graphicScene.removeItem(self._graphicsPaths[key]) del self._datasetItems[key] def hasPath(self,dataset): return dataset in self._graphicsPaths def updatePath(self,dataset): try: if dataset in self._graphicsPaths: try: self._graphicScene.removeItem(self._graphicsPaths[dataset]) except: pass del self._graphicsPaths[dataset] mpts = self._model.getPointsForDataset(dataset) modelPoints = [QtCore.QPointF(pos[0], pos[1]) for pos in mpts] if len(modelPoints) >=2: #zval = min([item.zValue() for item in items]) gpath = QtGui.QPainterPath() gpath.moveTo(modelPoints[0]) for i in range(1,len(modelPoints)): gpath.quadTo(modelPoints[i-1], modelPoints[i]) gpath.closeSubpath() #gpath.quadTo(modelPoints[-1], modelPoints[0]) #pen,brush = self.fillBrushes[dataset % len(self.PenColors)] pen,brush = self.datasetColors[dataset] gitem = self._graphicScene.addPath(gpath,pen,brush) gitem.setZValue(1) self._graphicsPaths[dataset] = gitem except: import traceback traceback.print_exc(file=sys.stdout) class PainterWidget(PainterWidgetBase): region, point = range(2) flipped = False def __init__(self,mode,parent=None,project=None): super(PainterWidget,self).__init__(parent) self.sceneLayout = QtGui.QVBoxLayout(self.graphicsViewHolder) self.graphicsView = PaintGraphicsView() self.sceneLayout.addWidget(self.graphicsView) self.filename = None self.model = PaintModel() self.model.modelChanged.connect(self.updateInterface) self._graphicsScene = PaintGraphicsScene(self) self.graphicsView.setScene(self._graphicsScene) self.addNewDataset.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_FileDialogNewFolder'))) self.removeCurrentDataset.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_MessageBoxCritical'))) self.moveUp.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_ArrowUp'))) self.moveDown.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_ArrowDown'))) self.saveData.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_DialogSaveButton'))) self.loadData.setIcon(self.style().standardIcon(getattr(QtGui.QStyle, 'SP_DialogOpenButton'))) self.addNewDataset.clicked.connect(self.addDataset) self.removeCurrentDataset.clicked.connect(self.removeDataset) #self.loadBackground.clicked.connect(self.newBackground) self.saveData.clicked.connect(self.saveRegions) self.loadData.clicked.connect(self.loadRegions) self.moveUp.clicked.connect(self.moveDatasetItemUp) self.moveDown.clicked.connect(self.moveDatasetItemDown) # Actions self.listDatasets.itemSelectionChanged.connect(self.selectedDatasetChanged) self._mode = mode if mode==self.region: self.viewMousePressEvent = self.viewMousePressEventRegion self.pointItemMoved = self.pointItemMovedRegion else: self.viewMousePressEvent = self.viewMousePressEventPoint self.pointItemMoved = self.pointItemMovedPoint # Mouse move self._graphicsScene.mousePressed.connect(self.viewMousePressEvent) self._graphicsScene.pointItemMoved.connect(self.pointItemMoved) self.loadproject(project) def moveDatasetItemUp(self): numItems = self.listDatasets.count() if numItems>1: currentRow = self.listDatasets.currentRow() if currentRow > 0: ci = self.listDatasets.takeItem(currentRow) self.listDatasets.insertItem(currentRow - 1, ci) self.listDatasets.setCurrentRow(currentRow - 1) def moveDatasetItemDown(self): numItems = self.listDatasets.count() if numItems>1: currentRow = self.listDatasets.currentRow() if currentRow < numItems-1: ci = self.listDatasets.takeItem(currentRow) self.listDatasets.insertItem(currentRow + 1, ci) self.listDatasets.setCurrentRow(currentRow + 1) def setupMeshBackground(self,backgroundMesh,axialElements): ''' Load a mesh and create a background based on the node that lie on xy-plane Assumes the standard Stomach Meshobject is used ''' self.model.newDataSet('Default') self.view = PaintView(self.model, self._graphicsScene, self.listDatasets) self.view.setMeshBackground(backgroundMesh,axialElements) #Rendering needs to be flipped along y, as qgraphicsscene coordinate system starts at top,left if not self.flipped: self.graphicsView.setflip(-1) self.flipped = True self.filename = None self.updateInterface() def getMarkersAndColors(self): result = self.model.getDatasetInTransformedCoordinates() #Order the result in the listed order orderedResult = OrderedDict() for row in range(self.listDatasets.count()): item = self.listDatasets.item(row) name = item._name orderedResult[name] = result[name] orderedColors = OrderedDict() for k,name in self.model._datasets.items(): p,_ = self.model.datasetColors[k] color = p.color() orderedColors[name] = [color.red(),color.green(),color.blue()] return orderedResult,orderedColors def saveRegions(self,filename=None): if hasattr(self, 'view'): if filename is None: filename = QtGui.QFileDialog.getSaveFileName(self, 'Dataset file', '.', "Data Files (*.dat)") if isinstance(filename,tuple): #Handle pyside filename = str(filename[0]) if filename is not None and not str(filename) == "": return self.model.saveDataset(filename) return False def loadRegions(self,filename=None): if hasattr(self, 'view'): if filename is None: filename = QtGui.QFileDialog.getOpenFileName(self, 'Dataset file', '.', "Data Files (*.dat)") if isinstance(filename,tuple): #Handle pyside filename = str(filename[0]) if filename is not None and not str(filename) == "": self.model.loadDataset(filename) self.view.setModel(self.model) if self._mode==self.region: for dataset in self.model._datasets: self.view.updatePath(dataset) def newBackground(self): filename = QtGui.QFileDialog.getOpenFileName(self, 'Select background image', '.', "Image Files (*.png *.jpg *.jpeg *.bmp *.tif)") if isinstance(filename,tuple): #Handle pyside filename = str(filename[0]) if filename is not None and not str(filename) == "": # Clean previous state if hasattr(self, 'view'): if hasattr(self.view, 'meshSilhouette') and not self.view.meshSilhouette is None : if self.flipped: self.graphicsView.setflip(-1) self.flipped = False self.model.addImage(str(filename)) self.view.updateBackground() else: self.model.newDataSet(filename) self.view = PaintView(self.model, self._graphicsScene, self.listDatasets) self.filename = filename else: self.filename = None self.updateInterface() def defaultProject(self): self.model.newDataSet('Default') self.view = PaintView(self.model, self._graphicsScene, self.listDatasets) self.filename = None self.updateInterface() def loadproject(self,filename): if filename is not None and not str(filename) == "": # Try to load project self.model.loadDataset(filename) self.view = PaintView(self.model, self._graphicsScene, self.listDatasets) self.filename = filename self.updateInterface() def updateInterface(self): self.graphicsView.setEnabled(True) def addDataset(self): self.model.addDataset() def removeDataset(self): row = self.listDatasets.currentRow() item = self.listDatasets.takeItem(row) self.model.removeDataset(item._key) def selectedDatasetChanged(self): if not self.listDatasets.currentItem() is None: ds = self.listDatasets.currentItem()._key self.model.changeCurrentDataset(ds) if hasattr(self, 'view'): self.view.listSelectionChanged(ds) else: self.model.changeCurrentDataset(None) def viewMousePressEventRegion(self, event, item): mouse_pos = event.scenePos(); mouse_pos = [mouse_pos.x(), mouse_pos.y()] #mode = self.model._mode currentDataset = self.model.getCurrentDataset() if event.button() == QtCore.Qt.LeftButton: if (item is None) or (item == self.view._background) or (item == self.view.meshSilhouette): self.model.addPoint(mouse_pos) elif isinstance(item, PaintGraphicsEllipseItem) and not self.view.hasPath(currentDataset): self.view.updatePath(currentDataset) elif event.button() == QtCore.Qt.RightButton: if (not item is None) and (item != self.view._background) and (item != self.view.meshSilhouette): if isinstance(item, PaintGraphicsEllipseItem): key = item.getItemKey() self.model.removePoint(key) if self.view.hasPath(currentDataset): self.view.updatePath(self.model.getCurrentDataset()) def viewMousePressEventPoint(self, event, item): mouse_pos = event.scenePos(); mouse_pos = [mouse_pos.x(), mouse_pos.y()] if event.button() == QtCore.Qt.LeftButton: if (item is None) or (item == self.view._background): self.model.addPoint(mouse_pos) elif event.button() == QtCore.Qt.RightButton: if (not item is None) and (item != self.view._background): if isinstance(item, PaintGraphicsEllipseItem): key = item.getItemKey() self.model.removePoint(key) def pointItemMovedRegion(self, key, pos): self.model.movePoint(key, [pos.x(), pos.y()]) self.view.updatePath(self.model.getCurrentDataset()) def pointItemMovedPoint(self, key, pos): self.model.movePoint(key, [pos.x(), pos.y()]) from digitiser.mapping import MeshMapper if __name__ == '__main__': # Entry point application = QtGui.QApplication(sys.argv) main_window = PainterWidget(0) main_window.show() #main_window.defaultProject() backgroundMesh = MeshMapper() #self.backgroundMesh.setupByFile(r'scaffoldmaker.ex2', 8,11) backgroundMesh.setupByPickle('symmetricstomachsurface.pkl') main_window.setupMeshBackground(backgroundMesh,11) #main_window.newBackground() sys.exit(application.exec_())