class SkyImagePlotItem(QwtPlotItem, QObject): """SkyImagePlotItem is a 2D image in l,m coordimnates""" def __init__(self, nx=0, ny=0, l0=0, m0=0, dl=1, dm=1, image=None): QwtPlotItem.__init__(self) # name, if any self.name = self.filename = None # internal init self._qo = QObject() self._image = self._imgminmax = None self._nvaluecalls = 0 self._value_time = self._value_time0 = None self._lminmax = (0, 0) self._mminmax = (0, 0) self._cache_qimage = {} self._cache_mapping = self._cache_imap = self._cache_interp = None self._psfsize = 0, 0, 0 # set image, if specified if image is not None: nx, ny = image.shape self.setImage(image) # set coordinates, if specified if nx and ny: self.setImageCoordinates(nx, ny, l0, m0, dl, dm) # set default colormap and intensity map self.colormap = Colormaps.GreyscaleColormap self.imap = Colormaps.LinearIntensityMap() def emit(self, *args): self._qo.emit(*args) def connect(self, *args): QObject.connect(self._qo, *args) def clearDisplayCache(self): """Clears all display caches.""" self._cache_qimage = {} self._cache_interp = self._cache_imap = None def setColorMap(self, cmap=None, emit=True): """Changes the colormap. If called with no arguments, clears colormap-dependent caches""" self._cache_qimage = {} if cmap: self.colormap = cmap if emit: self.emit(SIGNAL("repaint")) def updateCurrentColorMap(self): self._cache_qimage = {} self.emit(SIGNAL("repaint")) def setIntensityMap(self, imap=None, emit=True): """Changes the intensity map. If called with no arguments, clears intensity map-dependent caches""" self._cache_qimage = {} self._cache_imap = None if imap: self.imap = imap if emit: self.emit(SIGNAL("repaint")) def colorMap(self): return self.colormap def intensityMap(self): return self.imap def setImageCoordinates(self, nx, ny, x0, y0, l0, m0, dl, dm): """Sets up image coordinates. Pixel x0,y0 is centered at location l0,m0 in the plot, pixel size is dl,dm, image size is (nx,ny)""" dprint(2, "image coordinates are", nx, ny, x0, y0, l0, m0, dl, dm) self._nx, self._ny = nx, ny self._l0, self._m0 = l0, m0 self._dl, self._dm = dl, dm self._x0, self._y0 = x0, y0 self._lminmax = (l0 - dl * (x0 + 0.5), l0 + (nx - x0 - 0.5) * dl) if dl < 0: self._lminmax = (self._lminmax[1], self._lminmax[0]) self._mminmax = (m0 - dm * (y0 + 0.5), m0 + (ny - y0 - 0.5) * dm) self._bounding_rect = QRectF(self._lminmax[0], self._mminmax[0], nx * abs(dl), ny * abs(dm)) self._bounding_rect_pix = QRect(0, 0, nx, ny) dprint(2, "image extents are", self._lminmax, self._mminmax) def imageDims(self): """Returns image dimensions as mx,ny""" return self._nx, self._ny def referencePixel(self): return self._x0, self._y0 def lmToPix(self, l, m): """Converts l,m coordimnates to float (so possibly fractional) pixel coordinates.""" return self._x0 + (l - self._l0) / self._dl, self._y0 + ( m - self._m0) / self._dm def pixToLm(self, x, y): """Converts pixel coordinates to lm coordinates.""" return self._l0 + (x - self._x0) * self._dl, self._m0 + ( y - self._y0) * self._dm def getExtents(self): """Returns image extent, as (l0,l1),(m0,m1)""" return self._lminmax, self._mminmax def boundingRect(self): """Returns bouding rectangle of image, in lm coordinates.""" return self._bounding_rect def currentRect(self): """Returns currently visible rectange, in lm coordinates. Coordinates may be outside of image range.""" return self._current_rect def currentRectPix(self): """Returns currently visible rectange, in pixel coordinates. Pixel coordinates are bounded to 0,0 and nx-1,ny-1.""" return self._current_rect_pix def setImage(self, image, key=None, minmax=None): """Sets image array. If key is not None, sets this as the image key (for use with the pixmap cache.) If minmax is not None, then stores this as the (presumably cached or precomputed) min/max values. """ self._image = image self._imgminmax = minmax self._image_key = key # clear intermediate caches self._prefilter = self._cache_interp = self._cache_imap = None # if key is None, also clear QImage cache -- it only works when we have images identified by keys if key is None: self._cache_qimage = {} def image(self): """Returns image array.""" return self._image def imagePixel(self, x, y): if numpy.ma.isMA(self._image): return self._image.data[x, y], self._image.mask[x, y] else: return self._image[x, y], False def imageMinMax(self): if not self._imgminmax: dprint(3, "computing image min/max") rdata, rmask = self.optimalRavel(self._image) try: self._imgminmax = measurements.extrema( rdata, labels=rmask, index=None if rmask is None else False)[:2] except: # when all data is masked, some versions of extrema() throw an exception self._imgminmax = numpy.nan, numpy.nan dprint(3, self._imgminmax) return self._imgminmax def draw(self, painter, xmap, ymap, rect): """Implements QwtPlotItem.draw(), to render the image on the given painter.""" xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2( ), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist() yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2( ), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist() dprint(5, "draw:", rect, xinfo, yinfo) self._current_rect = QRectF(QPointF(xs2, ys1), QSizeF(xds, yds)) self._current_rect_pix = QRect( QPoint(*self.lmToPix(xs1, ys1)), QPoint(*self.lmToPix(xs2, ys2))).intersected( self._bounding_rect_pix) dprint(5, "draw:", self._current_rect_pix) # put together tuple describing current mapping mapping = xinfo, yinfo # if mapping has changed w.r.t. cache (i.e. zoom has changed), discard all cached QImages if mapping != self._cache_mapping: dprint(2, "does not match cached mapping, cache is:", self._cache_mapping) dprint(2, "and we have:", mapping) self.clearDisplayCache() self._cache_mapping = mapping t0 = time.time() # check cached QImage for current image key. qimg = self._cache_qimage.get(self._image_key) if qimg: dprint(5, "QImage found in cache, reusing") # else regenerate image else: # check for cached intensity-mapped data if self._cache_imap is not None: dprint(5, "intensity-mapped data found in cache, reusing") else: if self._cache_interp is not None: dprint(5, "interpolated data found in cache, reusing") else: image = self._image.transpose( ) if self._data_fortran_order else self._image spline_order = 2 xsamp = abs(xmap.sDist() / xmap.pDist()) / abs(self._dl) ysamp = abs(ymap.sDist() / ymap.pDist()) / abs(self._dm) if max(xsamp, ysamp) < .33 or min(xsamp, ysamp) > 2: spline_order = 1 dprint(2, "regenerating drawing cache, sampling factors are", xsamp, ysamp, "spline order is", spline_order) self._cache_imap = None if self._prefilter is None and spline_order > 1: self._prefilter = interpolation.spline_filter( image, order=spline_order) dprint(2, "spline prefiltering took", time.time() - t0, "secs") t0 = time.time() # make arrays of plot coordinates # xp[0],yp[0] corresponds to pixel 0,0, where 0,0 is the upper-left corner of the plot # the maps are in a funny order (w.r.t. meaning of p1/p2/s1/s2), so the indices here are determined empirically # We also adjust by half-pixel, to get the world coordinate of the pixel _center_ xp = xmap.s1() - (xmap.sDist() / xmap.pDist()) * ( 0.5 + numpy.arange(int(xmap.pDist()))) yp = ymap.s2() - (ymap.sDist() / ymap.pDist()) * ( 0.5 + numpy.arange(int(ymap.pDist()))) # now convert plot coordinates into fractional image pixel coordinates xi = self._x0 + (xp - self._l0) / self._dl yi = self._y0 + (yp - self._m0) / self._dm # interpolate image data ### # old code for nearest-neighbour interpolation ### # superceded by interpolation below (we simply round pixel coordinates to go to NN when oversampling) ### xi = xi.round().astype(int) ### oob_x = (xi<0)|(xi>=self._nx) ### xi[oob_x] = 0 ### yi = yi.round().astype(int) ### oob_y = (yi<0)|(yi>=self._ny) ### yi[oob_y] = 0 ### idx = (xi[:,numpy.newaxis]*self._ny + yi[numpy.newaxis,:]).ravel() ### interp_image = self._image.ravel()[idx].reshape((len(xi),len(yi))) ### interp_image[oob_x,:] = 0 ### interp_image[:,oob_y] = 0 ### self._qimage_cache = self.colormap.colorize(interp_image,self._img_range) ### self._qimage_cache_attrs = (rect,xinfo,yinfo) # if either axis is oversampled by a factor of 3 or more, switch to nearest-neighbour interpolation by rounding pixel values if xsamp < .33: xi = xi.round() if ysamp < .33: yi = yi.round() # make [2,nx,ny] array of interpolation coordinates xy = numpy.zeros((2, len(xi), len(yi))) xy[0, :, :] = xi[:, numpy.newaxis] xy[1, :, :] = yi[numpy.newaxis, :] # interpolate. Use NAN for out of range pixels... # for fortran order, tranpose axes for extra speed (flip XY around then) if self._data_fortran_order: xy = xy[-1::-1, ...] if spline_order > 1: interp_image = interpolation.map_coordinates( self._prefilter, xy, order=spline_order, cval=numpy.nan, prefilter=False) else: interp_image = interpolation.map_coordinates( image, xy, order=spline_order, cval=numpy.nan) # ...and put a mask on them (Colormap.colorize() will make these transparent). mask = ~numpy.isfinite(interp_image) self._cache_interp = numpy.ma.masked_array( interp_image, mask) dprint(2, "interpolation took", time.time() - t0, "secs") t0 = time.time() # ok, we have interpolated data in _cache_interp self._cache_imap = self.imap.remap(self._cache_interp) dprint(2, "intensity mapping took", time.time() - t0, "secs") t0 = time.time() # ok, we have intensity-mapped data in _cache_imap qimg = self.colormap.colorize(self._cache_imap) dprint(2, "colorizing took", time.time() - t0, "secs") t0 = time.time() # cache the qimage self._cache_qimage[self._image_key] = qimg.copy() # now draw the image t0 = time.time() painter.drawImage(xp1, yp2, qimg) dprint(2, "drawing took", time.time() - t0, "secs") def setPsfSize(self, maj, min, pa): self._psfsize = maj, min, pa def getPsfSize(self): return self._psfsize
def processEvents(self): QObject.emit(self, SIGNAL("signalProcessEvents"))
class SkyImagePlotItem(QwtPlotItem, QObject): """SkyImagePlotItem is a 2D image in l,m coordimnates""" def __init__(self, nx=0, ny=0, l0=0, m0=0, dl=1, dm=1, image=None): QwtPlotItem.__init__(self) # name, if any self.name = self.filename = None # internal init self._qo = QObject() self._image = self._imgminmax = None self._nvaluecalls = 0 self._value_time = self._value_time0 = None self._lminmax = (0, 0) self._mminmax = (0, 0) self._cache_qimage = {} self._cache_mapping = self._cache_imap = self._cache_interp = None self._psfsize = 0, 0, 0 # set image, if specified if image is not None: nx, ny = image.shape self.setImage(image) # set coordinates, if specified if nx and ny: self.setImageCoordinates(nx, ny, l0, m0, dl, dm) # set default colormap and intensity map self.colormap = Colormaps.GreyscaleColormap self.imap = Colormaps.LinearIntensityMap() def emit(self, *args): self._qo.emit(*args) def connect(self, *args): QObject.connect(self._qo, *args) def clearDisplayCache(self): """Clears all display caches.""" self._cache_qimage = {} self._cache_interp = self._cache_imap = None def setColorMap(self, cmap=None, emit=True): """Changes the colormap. If called with no arguments, clears colormap-dependent caches""" self._cache_qimage = {} if cmap: self.colormap = cmap if emit: self.emit(SIGNAL("repaint")) def updateCurrentColorMap(self): self._cache_qimage = {} self.emit(SIGNAL("repaint")) def setIntensityMap(self, imap=None, emit=True): """Changes the intensity map. If called with no arguments, clears intensity map-dependent caches""" self._cache_qimage = {} self._cache_imap = None if imap: self.imap = imap if emit: self.emit(SIGNAL("repaint")) def colorMap(self): return self.colormap def intensityMap(self): return self.imap def setImageCoordinates(self, nx, ny, x0, y0, l0, m0, dl, dm): """Sets up image coordinates. Pixel x0,y0 is centered at location l0,m0 in the plot, pixel size is dl,dm, image size is (nx,ny)""" dprint(2, "image coordinates are", nx, ny, x0, y0, l0, m0, dl, dm) self._nx, self._ny = nx, ny self._l0, self._m0 = l0, m0 self._dl, self._dm = dl, dm self._x0, self._y0 = x0, y0 self._lminmax = (l0 - dl * (x0 + 0.5), l0 + (nx - x0 - 0.5) * dl) if dl < 0: self._lminmax = (self._lminmax[1], self._lminmax[0]) self._mminmax = (m0 - dm * (y0 + 0.5), m0 + (ny - y0 - 0.5) * dm) self._bounding_rect = QRectF(self._lminmax[0], self._mminmax[0], nx * abs(dl), ny * abs(dm)) self._bounding_rect_pix = QRect(0, 0, nx, ny) dprint(2, "image extents are", self._lminmax, self._mminmax) def imageDims(self): """Returns image dimensions as mx,ny""" return self._nx, self._ny def referencePixel(self): return self._x0, self._y0 def lmToPix(self, l, m): """Converts l,m coordimnates to float (so possibly fractional) pixel coordinates.""" return self._x0 + (l - self._l0) / self._dl, self._y0 + (m - self._m0) / self._dm def pixToLm(self, x, y): """Converts pixel coordinates to lm coordinates.""" return self._l0 + (x - self._x0) * self._dl, self._m0 + (y - self._y0) * self._dm def getExtents(self): """Returns image extent, as (l0,l1),(m0,m1)""" return self._lminmax, self._mminmax def boundingRect(self): """Returns bouding rectangle of image, in lm coordinates.""" return self._bounding_rect def currentRect(self): """Returns currently visible rectange, in lm coordinates. Coordinates may be outside of image range.""" return self._current_rect def currentRectPix(self): """Returns currently visible rectange, in pixel coordinates. Pixel coordinates are bounded to 0,0 and nx-1,ny-1.""" return self._current_rect_pix def setImage(self, image, key=None, minmax=None): """Sets image array. If key is not None, sets this as the image key (for use with the pixmap cache.) If minmax is not None, then stores this as the (presumably cached or precomputed) min/max values. """ self._image = image self._imgminmax = minmax self._image_key = key # clear intermediate caches self._prefilter = self._cache_interp = self._cache_imap = None # if key is None, also clear QImage cache -- it only works when we have images identified by keys if key is None: self._cache_qimage = {} def image(self): """Returns image array.""" return self._image def imagePixel(self, x, y): if numpy.ma.isMA(self._image): return self._image.data[x, y], self._image.mask[x, y] else: return self._image[x, y], False def imageMinMax(self): if not self._imgminmax: dprint(3, "computing image min/max") rdata, rmask = self.optimalRavel(self._image) try: self._imgminmax = measurements.extrema(rdata, labels=rmask, index=None if rmask is None else False)[:2] except: # when all data is masked, some versions of extrema() throw an exception self._imgminmax = numpy.nan, numpy.nan dprint(3, self._imgminmax) return self._imgminmax def draw(self, painter, xmap, ymap, rect): """Implements QwtPlotItem.draw(), to render the image on the given painter.""" xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2(), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist() yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist() dprint(5, "draw:", rect, xinfo, yinfo) self._current_rect = QRectF(QPointF(xs2, ys1), QSizeF(xds, yds)) self._current_rect_pix = QRect(QPoint(*self.lmToPix(xs1, ys1)), QPoint(*self.lmToPix(xs2, ys2))).intersected( self._bounding_rect_pix) dprint(5, "draw:", self._current_rect_pix) # put together tuple describing current mapping mapping = xinfo, yinfo # if mapping has changed w.r.t. cache (i.e. zoom has changed), discard all cached QImages if mapping != self._cache_mapping: dprint(2, "does not match cached mapping, cache is:", self._cache_mapping) dprint(2, "and we have:", mapping) self.clearDisplayCache() self._cache_mapping = mapping t0 = time.time() # check cached QImage for current image key. qimg = self._cache_qimage.get(self._image_key) if qimg: dprint(5, "QImage found in cache, reusing") # else regenerate image else: # check for cached intensity-mapped data if self._cache_imap is not None: dprint(5, "intensity-mapped data found in cache, reusing") else: if self._cache_interp is not None: dprint(5, "interpolated data found in cache, reusing") else: image = self._image.transpose() if self._data_fortran_order else self._image spline_order = 2 xsamp = abs(xmap.sDist() / xmap.pDist()) / abs(self._dl) ysamp = abs(ymap.sDist() / ymap.pDist()) / abs(self._dm) if max(xsamp, ysamp) < .33 or min(xsamp, ysamp) > 2: spline_order = 1 dprint(2, "regenerating drawing cache, sampling factors are", xsamp, ysamp, "spline order is", spline_order) self._cache_imap = None if self._prefilter is None and spline_order > 1: self._prefilter = interpolation.spline_filter(image, order=spline_order) dprint(2, "spline prefiltering took", time.time() - t0, "secs") t0 = time.time() # make arrays of plot coordinates # xp[0],yp[0] corresponds to pixel 0,0, where 0,0 is the upper-left corner of the plot # the maps are in a funny order (w.r.t. meaning of p1/p2/s1/s2), so the indices here are determined empirically # We also adjust by half-pixel, to get the world coordinate of the pixel _center_ xp = xmap.s1() - (xmap.sDist() / xmap.pDist()) * (0.5 + numpy.arange(int(xmap.pDist()))) yp = ymap.s2() - (ymap.sDist() / ymap.pDist()) * (0.5 + numpy.arange(int(ymap.pDist()))) # now convert plot coordinates into fractional image pixel coordinates xi = self._x0 + (xp - self._l0) / self._dl yi = self._y0 + (yp - self._m0) / self._dm # interpolate image data ### # old code for nearest-neighbour interpolation ### # superceded by interpolation below (we simply round pixel coordinates to go to NN when oversampling) ### xi = xi.round().astype(int) ### oob_x = (xi<0)|(xi>=self._nx) ### xi[oob_x] = 0 ### yi = yi.round().astype(int) ### oob_y = (yi<0)|(yi>=self._ny) ### yi[oob_y] = 0 ### idx = (xi[:,numpy.newaxis]*self._ny + yi[numpy.newaxis,:]).ravel() ### interp_image = self._image.ravel()[idx].reshape((len(xi),len(yi))) ### interp_image[oob_x,:] = 0 ### interp_image[:,oob_y] = 0 ### self._qimage_cache = self.colormap.colorize(interp_image,self._img_range) ### self._qimage_cache_attrs = (rect,xinfo,yinfo) # if either axis is oversampled by a factor of 3 or more, switch to nearest-neighbour interpolation by rounding pixel values if xsamp < .33: xi = xi.round() if ysamp < .33: yi = yi.round() # make [2,nx,ny] array of interpolation coordinates xy = numpy.zeros((2, len(xi), len(yi))) xy[0, :, :] = xi[:, numpy.newaxis] xy[1, :, :] = yi[numpy.newaxis, :] # interpolate. Use NAN for out of range pixels... # for fortran order, tranpose axes for extra speed (flip XY around then) if self._data_fortran_order: xy = xy[-1::-1, ...] if spline_order > 1: interp_image = interpolation.map_coordinates(self._prefilter, xy, order=spline_order, cval=numpy.nan, prefilter=False) else: interp_image = interpolation.map_coordinates(image, xy, order=spline_order, cval=numpy.nan) # ...and put a mask on them (Colormap.colorize() will make these transparent). mask = ~numpy.isfinite(interp_image) self._cache_interp = numpy.ma.masked_array(interp_image, mask) dprint(2, "interpolation took", time.time() - t0, "secs") t0 = time.time() # ok, we have interpolated data in _cache_interp self._cache_imap = self.imap.remap(self._cache_interp) dprint(2, "intensity mapping took", time.time() - t0, "secs") t0 = time.time() # ok, we have intensity-mapped data in _cache_imap qimg = self.colormap.colorize(self._cache_imap) dprint(2, "colorizing took", time.time() - t0, "secs") t0 = time.time() # cache the qimage self._cache_qimage[self._image_key] = qimg.copy() # now draw the image t0 = time.time() painter.drawImage(xp1, yp2, qimg) dprint(2, "drawing took", time.time() - t0, "secs") def setPsfSize(self, maj, min, pa): self._psfsize = maj, min, pa def getPsfSize(self): return self._psfsize