def __init__(self, sliceShape, data2scene=QTransform(), blockSize=512, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): """ Args: sliceShape -- (width, height) data2scene -- QTransform from data to image coordinates (default: identity transform) blockSize -- base tile size: blockSize x blockSize (default 256) overlap -- overlap between tiles positive number prevents rendering artifacts between tiles for certain zoom levels (default 1) """ self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None]*numPatches self.dataRectFs = [None]*numPatches self.tileRectFs = [None]*numPatches self.imageRects = [None]*numPatches self.dataRects = [None]*numPatches self.tileRects = [None]*numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene
def __init__(self, sliceShape, data2scene=QTransform(), blockSize=256, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None] * numPatches self.dataRectFs = [None] * numPatches self.tileRectFs = [None] * numPatches self.imageRects = [None] * numPatches self.dataRects = [None] * numPatches self.tileRects = [None] * numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene
def __init__(self, sliceShape, data2scene=QTransform(), blockSize=256, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): """ Args: sliceShape -- (width, height) data2scene -- QTransform from data to image coordinates (default: identity transform) blockSize -- base tile size: blockSize x blockSize (default 256) overlap -- overlap between tiles positive number prevents rendering artifacts between tiles for certain zoom levels (default 1) """ self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None]*numPatches self.dataRectFs = [None]*numPatches self.tileRectFs = [None]*numPatches self.imageRects = [None]*numPatches self.dataRects = [None]*numPatches self.tileRects = [None]*numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene
def __init__(self, sliceShape, data2scene=QTransform(), blockSize=256, overlap=1): self.blockSize = blockSize self.overlap = 1 patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self.imageRectFs = [] self.tileRectFs = [] self.imageRects = [] self.tileRects = [] self.sliceShape = sliceShape for patchNr in range(patchAccessor.patchCount): #the patch accessor uses the data coordinate system # #because the patch is drawn on the screen, its holds coordinates #corresponding to Qt's QGraphicsScene's system, which need to be #converted to scene coordinates #the image rectangle includes an overlap margin imageRectF = data2scene.mapRect(patchAccessor.patchRectF(patchNr, self.overlap)) #the patch rectangle has no overlap patchRectF = data2scene.mapRect(patchAccessor.patchRectF(patchNr, 0)) patchRect = QRect(round(patchRectF.x()), round(patchRectF.y()), \ round(patchRectF.width()), round(patchRectF.height())) #the image rectangles of neighboring patches can overlap slightly, to account #for inaccuracies in sub-pixel rendering of many ImagePatch objects imageRect = QRect(round(imageRectF.x()), round(imageRectF.y()), \ round(imageRectF.width()), round(imageRectF.height())) self.imageRectFs.append(imageRectF) self.tileRectFs.append(patchRectF) self.imageRects.append(imageRect) self.tileRects.append(patchRect)
def shape(self, shape2D): assert len(shape2D) == 2 self.setSceneRect(0,0, *shape2D) del self._renderThread del self.imagePatches self._patchAccessor = PatchAccessor(self.shape[1], self.shape[0], blockSize=self.blockSize) self.imagePatches = [[] for i in range(self._patchAccessor.patchCount)] self._renderThread = ImageSceneRenderThread(self.imagePatches, self.stackedImageSources, parent=self) self._renderThread.start() self._renderThread.patchAvailable.connect(self._schedulePatchRedraw) self._initializePatches()
def __init__(self, sliceShape, data2scene=QTransform(), blockSize=256, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None]*numPatches self.dataRectFs = [None]*numPatches self.tileRectFs = [None]*numPatches self.imageRects = [None]*numPatches self.dataRects = [None]*numPatches self.tileRects = [None]*numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene
class Tiling(object): '''Tiling.__init__() Arguments: sliceShape -- (width, height) data2scene -- QTransform from data to image coordinates (default: identity transform) blockSize -- base tile size: blockSize x blockSize (default 256) overlap -- overlap between tiles positive number prevents rendering artifacts between tiles for certain zoom levels (default 1) ''' def __init__(self, sliceShape, data2scene=QTransform(), blockSize=256, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None]*numPatches self.dataRectFs = [None]*numPatches self.tileRectFs = [None]*numPatches self.imageRects = [None]*numPatches self.dataRects = [None]*numPatches self.tileRects = [None]*numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene @property def data2scene(self): return self._data2scene @data2scene.setter def data2scene(self, data2scene): self._data2scene = data2scene self.scene2data, isInvertible = data2scene.inverted() assert isInvertible for patchNr in range(self._patchAccessor.patchCount): # the patch accessor uses the data coordinate system. # because the patch is drawn on the screen, its holds coordinates # corresponding to Qt's QGraphicsScene's system, which need to be # converted to scene coordinates # the image rectangle includes an overlap margin imageRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, self.overlap)) # the patch rectangle has per default no overlap patchRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, 0)) # add a little overlap when the overlap_draw setting is # activated if self._overlap_draw != 0: patchRectF = QRectF(patchRectF.x() - self._overlap_draw, patchRectF.y() - self._overlap_draw, patchRectF.width() + 2 * self._overlap_draw, patchRectF.height() + 2 * self._overlap_draw) patchRect = QRect(round(patchRectF.x()), round(patchRectF.y()), round(patchRectF.width()), round(patchRectF.height())) # the image rectangles of neighboring patches can overlap # slightly, to account for inaccuracies in sub-pixel # rendering of many ImagePatch objects imageRect = QRect(round(imageRectF.x()), round(imageRectF.y()), round(imageRectF.width()), round(imageRectF.height())) self.imageRectFs[patchNr] = imageRectF self.dataRectFs[ patchNr] = imageRectF self.tileRectFs[ patchNr] = patchRectF self.imageRects[ patchNr] = imageRect self.tileRects[ patchNr] = patchRect def boundingRectF(self): if self.tileRectFs: p = self.tileRectFs[-1] br = QRectF(0,0, p.x()+p.width(), p.y()+p.height()) else: br = QRectF(0,0,0,0) return br def containsF(self, point): for i, p in enumerate(self.tileRectFs): if p.contains(point): return i def intersected(self, sceneRect): if not sceneRect.isValid(): return range(len(self.tileRects)) # Patch accessor uses data coordinates rect = self.data2scene.inverted()[0].mapRect(sceneRect) patchNumbers = self._patchAccessor.getPatchesForRect( rect.topLeft().x(), rect.topLeft().y(), rect.bottomRight().x(), rect.bottomRight().y() ) return patchNumbers def __len__(self): return len(self.imageRectFs)
class Tiling(object): """ Describes the geometry of a tiling, for easy access to patch rects, overall shape, tile size, and data2scene transform. """ def __init__(self, sliceShape, data2scene=QTransform(), blockSize=512, overlap=0, overlap_draw=1e-3, name="Unnamed Tiling"): """ Args: sliceShape -- (width, height) data2scene -- QTransform from data to image coordinates (default: identity transform) blockSize -- base tile size: blockSize x blockSize (default 256) overlap -- overlap between tiles positive number prevents rendering artifacts between tiles for certain zoom levels (default 1) """ self.blockSize = blockSize self.overlap = overlap self._patchAccessor = PatchAccessor(sliceShape[0], sliceShape[1], blockSize=self.blockSize) self._overlap_draw = overlap_draw self._overlap = overlap numPatches = self._patchAccessor.patchCount self.imageRectFs = [None]*numPatches self.dataRectFs = [None]*numPatches self.tileRectFs = [None]*numPatches self.imageRects = [None]*numPatches self.dataRects = [None]*numPatches self.tileRects = [None]*numPatches self.sliceShape = sliceShape self.name = name self.data2scene = data2scene @property def data2scene(self): return self._data2scene @data2scene.setter def data2scene(self, data2scene): self._data2scene = data2scene self.scene2data, isInvertible = data2scene.inverted() assert isInvertible for patchNr in range(self._patchAccessor.patchCount): # the patch accessor uses the data coordinate system. # because the patch is drawn on the screen, its holds coordinates # corresponding to Qt's QGraphicsScene's system, which need to be # converted to scene coordinates # the image rectangle includes an overlap margin imageRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, self.overlap)) # the patch rectangle has per default no overlap patchRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, 0)) # add a little overlap when the overlap_draw setting is # activated if self._overlap_draw != 0: patchRectF = QRectF(patchRectF.x() - self._overlap_draw, patchRectF.y() - self._overlap_draw, patchRectF.width() + 2 * self._overlap_draw, patchRectF.height() + 2 * self._overlap_draw) patchRect = QRect(round(patchRectF.x()), round(patchRectF.y()), round(patchRectF.width()), round(patchRectF.height())) # the image rectangles of neighboring patches can overlap # slightly, to account for inaccuracies in sub-pixel # rendering of many ImagePatch objects imageRect = QRect(round(imageRectF.x()), round(imageRectF.y()), round(imageRectF.width()), round(imageRectF.height())) self.imageRectFs[patchNr] = imageRectF self.dataRectFs[ patchNr] = imageRectF self.tileRectFs[ patchNr] = patchRectF self.imageRects[ patchNr] = imageRect self.tileRects[ patchNr] = patchRect def boundingRectF(self): if self.tileRectFs: p = self.tileRectFs[-1] br = QRectF(0,0, p.x()+p.width(), p.y()+p.height()) else: br = QRectF(0,0,0,0) return br def containsF(self, point): for i, p in enumerate(self.tileRectFs): if p.contains(point): return i def intersected(self, sceneRect): if not sceneRect.isValid(): return range(len(self.tileRects)) # Patch accessor uses data coordinates rect = self.data2scene.inverted()[0].mapRect(sceneRect) patchNumbers = self._patchAccessor.getPatchesForRect( rect.topLeft().x(), rect.topLeft().y(), rect.bottomRight().x(), rect.bottomRight().y() ) return patchNumbers def __len__(self): return len(self.imageRectFs)
class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ # base patch size: blockSize x blockSize blockSize = 128 # overlap between patches # positive number prevents rendering artifacts between patches for certain zoom levels # increases the base blockSize overlap = 1 # update delay when a new patch arrives in ms glUpdateDelay = 10 @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s s.isDirty.connect(self._invalidateRect) self._initializePatches() #s.stackChanged.connect(self._initializePatches) s.stackChanged.connect(partial(self._invalidateRect, QRect())) @property def shape(self): return (self.sceneRect().width(), self.sceneRect().height()) @shape.setter def shape(self, shape2D): assert len(shape2D) == 2 self.setSceneRect(0,0, *shape2D) del self._renderThread del self.imagePatches self._patchAccessor = PatchAccessor(self.shape[1], self.shape[0], blockSize=self.blockSize) self.imagePatches = [[] for i in range(self._patchAccessor.patchCount)] self._renderThread = ImageSceneRenderThread(self.imagePatches, self.stackedImageSources, parent=self) self._renderThread.start() self._renderThread.patchAvailable.connect(self._schedulePatchRedraw) self._initializePatches() def __init__( self ): QGraphicsScene.__init__(self) self._glWidget = None self._useGL = False self._updatableTiles = [] # tile rendering self.imagePatches = None self._renderThread = None self._stackedImageSources = None self._numLayers = 0 #current number of 'layers' def cleanup(): self._renderThread.stop() self.destroyed.connect(cleanup) def activateOpenGL( self, qglwidget ): self._useGL = True self._glWidget = qglwidget glDisable(GL_DEPTH_TEST) glEnable(GL_TEXTURE_2D) glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT) def deactivateOpenGL( self ): self._useGL = False self._glWidget = None def _initializePatches(self): if self.stackedImageSources is None or self.shape == (0.0, 0.0): return if len(self.stackedImageSources) != self._numLayers: self._numLayers = len(self.stackedImageSources) #add an additional layer for the final composited image patch for i in range(self._patchAccessor.patchCount): r = self._patchAccessor.patchRectF(i, self.overlap) patches = [ImagePatch(r) for j in range(self._numLayers+1)] self.imagePatches[i] = patches def _invalidateRect(self, rect = QRect()): if not rect.isValid(): #everything is invalidated #we cancel all requests self._renderThread.cancelAll() self._updatableTiles = [] if self._stackedImageSources is not None and self._numLayers != len(self._stackedImageSources): self._initializePatches() for i,patch in enumerate(self.imagePatches): if not rect.isValid() or rect.intersects(patch[self._numLayers].rect): ##convention: if a rect is invalid, it is infinitely large patch[self._numLayers].dirty = True self._schedulePatchRedraw(i) def _schedulePatchRedraw(self, patchNr): p = self.imagePatches[patchNr][self._numLayers] self._updatableTiles.append(patchNr) if not self._useGL: self.invalidate(p.rectF, QGraphicsScene.BackgroundLayer) else: QTimer.singleShot(self.glUpdateDelay, self.update) def drawBackgroundSoftware(self, painter, rect): drawnTiles = 0 for patches in self.imagePatches: patch = patches[self._numLayers] if not patch.rectF.intersect(rect): continue patch.mutex.lock() painter.drawImage(patch.rectF.topLeft(), patch.image) patch.mutex.unlock() drawnTiles +=1 #print "ImageView2D.drawBackgroundSoftware: drew %d of %d tiles" % (drawnTiles, len(self.imagePatches)) def drawBackgroundGL(self, painter, rect): painter.beginNativePainting() #This will clear the screen, but also introduce flickering glClearColor(0.0, 1.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); #update the textures of those patches that were updated for t in self._updatableTiles: patch = self.imagePatches[t][self._numLayers] if patch.texture > -1: self._glWidget.deleteTexture(patch.texture) patch.texture = self._glWidget.bindTexture(patch.image) #see 'backingstore' example by Ariya Hidayat glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) #this ensures a seamless transition between tiles glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) self._updatableTiles = [] drawnTiles = 0 for patches in self.imagePatches: patch = patches[self._numLayers] if not patch.rectF.intersect(rect): continue patch.drawTexture() drawnTiles +=1 #print "ImageView2D.drawBackgroundGL: drew %d of %d tiles" % (drawnTiles, len(self.imagePatches)) painter.endNativePainting() def drawBackground(self, painter, rect): #Abandon previous workloads #FIXME FIXME #self._renderThread.queue.clear() #self._renderThread.newerDataPending.set() #Find all patches that intersect the given 'rect'. for i,patch in enumerate(self.imagePatches): patch = patch[self._numLayers] if patch.dirty and rect.intersects(patch.rectF): self._renderThread.requestPatch(i) if self._useGL: self.drawBackgroundGL(painter, rect) else: self.drawBackgroundSoftware(painter, rect)