class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s s.sizeChanged.connect(self._onSizeChanged) @property def showTileOutlines(self): return self._showTileOutlines @showTileOutlines.setter def showTileOutlines(self, show): self._showTileOutlines = show self.invalidate() @property def sceneShape(self): """ The shape of the scene in QGraphicsView's coordinate system. """ return (self.sceneRect().width(), self.sceneRect().height()) @sceneShape.setter def sceneShape(self, sceneShape): """ Set the size of the scene in QGraphicsView's coordinate system. sceneShape -- (widthX, widthY), where the origin of the coordinate system is in the upper left corner of the screen and 'x' points right and 'y' points down """ assert len(sceneShape) == 2 self.setSceneRect(0,0, *sceneShape) #The scene shape is in Qt's QGraphicsScene coordinate system, #that is the origin is in the top left of the screen, and the #'x' axis points to the right and the 'y' axis down. #The coordinate system of the data handles things differently. #The x axis points down and the y axis points to the right. r = self.scene2data.mapRect(QRect(0,0,sceneShape[0], sceneShape[1])) sliceShape = (r.width(), r.height()) if self._dirtyIndicator: self.removeItem(self._dirtyIndicator) del self._dirtyIndicator self._dirtyIndicator = None self._tiling = Tiling(sliceShape, self.data2scene) self._dirtyIndicator = DirtyIndicator(self._tiling) self.addItem(self._dirtyIndicator) self._onSizeChanged() if self._tileProvider: self._tileProvider.notifyThreadsToStop() # prevent ref cycle self._tileProvider = TileProvider(self._tiling, self._stackedImageSources) self._tileProvider.changed.connect(self.invalidateViewports) def invalidateViewports( self, rectF ): '''Call invalidate on the intersection of all observing viewport-rects and rectF.''' rectF = rectF if rectF.isValid() else self.sceneRect() for view in self.views(): QGraphicsScene.invalidate( self, rectF.intersected(view.viewportRect()) ) def __init__( self, parent=None ): QGraphicsScene.__init__( self, parent=parent ) # tiled rendering of patches self._tiling = None self._brushingLayer = None # indicates the dirtyness of each tile self._dirtyIndicator = None self._tileProvider = None self._stackedImageSources = None self._showTileOutlines = False self.data2scene = QTransform(0,1,1,0,0,0) self.scene2data = self.data2scene.transposed() self._slicingPositionSettled = True self._redrawIndicator = False def drawLine(self, fromPoint, toPoint, pen): tileId = self._tiling.containsF(toPoint) if tileId is None: return p = self._brushingLayer[tileId] p.lock() painter = QPainter(p.image) painter.setPen(pen) tL = self._tiling.imageRectFs[tileId].topLeft() painter.drawLine(fromPoint-tL, toPoint-tL) painter.end() p.dataVer += 1 p.unlock() self.scheduleRedraw(self._tiling.imageRectFs[tileId]) def _onSizeChanged(self): self._brushingLayer = TiledImageLayer(self._tiling) def drawForeground(self, painter, rect): if self._tiling is None: return tile_nos = self._tiling.intersectedF(rect) for tileId in tile_nos: p = self._brushingLayer[tileId] if p.dataVer == p.imgVer: continue p.paint(painter) #access to the underlying image patch is serialized ## draw tile outlines if self._showTileOutlines: painter.drawRect(self._tiling.imageRects[tileId]) def indicateSlicingPositionSettled(self, settled): self._dirtyIndicator.setVisible(settled) self._slicingPositionSettled = settled def drawBackground(self, painter, rectF): if self._tileProvider is None: return tiles = self._tileProvider.getTiles(rectF) for tile in tiles: # prevent flickering if not tile.progress < 1.0: painter.drawImage(tile.rectF, tile.qimg) self._dirtyIndicator.setTileProgress(tile.id, tile.progress) def joinRendering( self ): return self._tileProvider.join()