def array_to_qimage(arr, copy=False): """Convert NumPy array to QImage object""" # https://gist.githubusercontent.com/smex/5287589/raw/toQImage.py if arr is None: return QImage() if len(arr.shape) not in (2, 3): raise NotImplementedError("Unsupported array shape %r" % arr.shape) data = arr.data ny, nx = arr.shape[:2] stride = arr.strides[0] # bytes per line color_dim = None if len(arr.shape) == 3: color_dim = arr.shape[2] if arr.dtype == np.uint8: if color_dim is None: qimage = QImage(data, nx, ny, stride, QImage.Format_Indexed8) # qimage.setColorTable([qRgb(i, i, i) for i in range(256)]) qimage.setColorCount(256) elif color_dim == 3: qimage = QImage(data, nx, ny, stride, QImage.Format_RGB888) elif color_dim == 4: qimage = QImage(data, nx, ny, stride, QImage.Format_ARGB32) else: raise TypeError("Invalid third axis dimension (%r)" % color_dim) elif arr.dtype == np.uint32: qimage = QImage(data, nx, ny, stride, QImage.Format_ARGB32) else: raise NotImplementedError("Unsupported array data type %r" % arr.dtype) if copy: return qimage.copy() return qimage
class QwtPlotImage(QwtPlotItem): def __init__(self, parent, title =QwtText()): QwtPlotItem.__init__(self) self.plot = parent self.display_type = "hippo" self.ValueAxis = None self.ComplexColorMap = None self._flags_array = None self._nan_flags_array = None self._image_for_display = None self._display_flags = False self.Qimage = None self.r_cmax = None self.r_cmin = None self.i_cmax = None self.i_cmin = None self.raw_image = None self.dimap = None self.complex = False self.log_scale = False self.log_y_scale = False self.transform_offset = 0.0 self.flag_colour = 0 self.nan_colour = 255 self.lock_image_real = False self.lock_image_imag = False self.setTitle(title) # __init__() def setDisplayType(self, display_type): self.display_type = display_type if HAS_TIMBA: _dprint(2,'display type set to ', self.display_type); # setDisplayType def setFlagColour(self, flag_colour): self.flag_colour = flag_colour def setLockImage(self, real = True, lock_image=False): if real: self.lock_image_real = lock_image else: self.lock_image_imag = lock_image # When set to True, the log_scale parameter will cause # the displayed image to be scaled logarithmically. def setLogScale(self, log_scale = True): self.log_scale = log_scale if self.log_scale == False: self.dimap = None self.transform_offset = 0.0 # When set to True, the log_y_scale parameter will cause # the output Y coordinate axis to be scaled properly for a Log plot # (used by QwtColorBar.py) def setLogYScale(self, log_y_scale = True): self.log_y_scale = log_y_scale def setFlagsArray(self, flags_array): self._flags_array = flags_array def setNanFlagsArray(self, flags_array): self._nan_flags_array = flags_array def setDisplayFlag(self, display_flags): self._display_flags = display_flags # setDisplayFlag def removeFlags(self): self._flags_array = None self._nan_flags_array = None # removeFlags def getRealImageRange(self): try: if self.raw_image.dtype == numpy.complex64 or self.raw_image.dtype == numpy.complex128: real_array = self.raw_image.real else: real_array = self.raw_image return (self.r_cmin, self.r_cmax, real_array.min(),real_array.max()) except: return (self.r_cmin, self.r_cmax, None, None) # getRealImageRange def getImagImageRange(self): try: if self.raw_image.dtype == numpy.complex64 or self.raw_image.dtype == numpy.complex128: imag_array = self.raw_image.imag return (self.i_cmin, self.i_cmax,imag_array.min(), imag_array.max()) except: return (self.i_cmin, self.i_cmax, None, None) # getRealImageRange def defineImageRange(self, limits, real=True): min = limits[0] max = limits[1] if abs(max - min) < 0.00005: if max == 0.0 or min == 0.0: min = -0.1 max = 0.1 else: min = 0.9 * min max = 1.1 * max if min > max: temp = max max = min min = temp if real: if not self.lock_image_real: self.r_cmin = min self.r_cmax = max else: if not self.lock_image_imag: self.i_cmin = min self.i_cmax = max def setImageRange(self, image): self.raw_image = image if image.dtype == numpy.complex64 or image.dtype == numpy.complex128: self.complex = True imag_array = image.imag real_array = image.real try: min = real_array.min() max = real_array.max() except: min = 0.0 max = 0.0 if abs(max - min) < 0.00005: if max == 0.0 or min == 0.0: min = -0.1 max = 0.1 else: min = 0.9 * min max = 1.1 * max if min > max: temp = max max = min min = temp if not self.lock_image_real: self.r_cmin = min self.r_cmax = max try: min = imag_array.min() max = imag_array.max() except: min = 0.0 max = 0.0 if abs(max - min) < 0.00005: if max == 0.0 or min == 0.0: min = -0.1 max = 0.1 else: min = 0.9 * min max = 1.1 * max if min > max: temp = max max = min min = temp if not self.lock_image_imag: self.i_cmin = min self.i_cmax = max else: self.complex = False try: min = image.min() max = image.max() except: min = 0.0 max = 0.0 if abs(max - min) < 0.00005: if max == 0.0 or min == 0.0: min = -0.1 max = 0.1 else: min = 0.9 * min max = 1.1 * max if min > max: temp = max max = min min = temp if not self.lock_image_real: self.r_cmin = min self.r_cmax = max # setImageRange def updateImage(self, image): self.setImage(image) self.raw_image = image def setFlaggedImageRange(self): (nx,ny) = self.raw_image.shape num_elements = nx * ny if self._flags_array is None: if not self._nan_flags_array is None: flags_array = self._nan_flags_array.copy() else: flags_array = numpy.zeros((nx,ny),int); else: flags_array = self._flags_array.copy() if not self._nan_flags_array is None: flags_array = flags_array + self._nan_flags_array flattened_flags = numpy.reshape(flags_array,(num_elements,)) if self.raw_image.dtype == numpy.complex64 or self.raw_image.dtype == numpy.complex128: real_array = self.raw_image.real imag_array = self.raw_image.imag flattened_real_array = numpy.reshape(real_array.copy(),(num_elements,)) flattened_imag_array = numpy.reshape(imag_array.copy(),(num_elements,)) real_flagged_array = numpy.compress(flattened_flags == 0, flattened_real_array) imag_flagged_array = numpy.compress(flattened_flags == 0, flattened_imag_array) flagged_image = numpy.zeros(shape=real_flagged_array.shape,dtype=self.raw_image.dtype) flagged_image.real = real_flagged_array flagged_image.imag = imag_flagged_array else: flattened_array = numpy.reshape(self.raw_image.copy(),(num_elements,)) flagged_image = numpy.compress(flattened_flags == 0, flattened_array) self.setImageRange(flagged_image) # setFlaggedImageRange def convert_to_log(self, incoming_image): self.transform_offset = 0.0 transform_image = incoming_image image_min = incoming_image.min() if image_min <= 0.0: image_min = -1.0 * image_min self.transform_offset = 0.001 + image_min transform_image = self.transform_offset + incoming_image scale_min = transform_image.min() scale_max = transform_image.max() if scale_min == scale_max: scale_min = scale_min - 0.5 * scale_min scale_max = scale_max + 0.5 * scale_min self.dimap = ImageScaler(1, 256, scale_min, scale_max, True) if HAS_TIMBA: _dprint(3, 'doing log transform of ', transform_image) temp_image = self.dimap.iTransform(transform_image) if HAS_TIMBA: _dprint(3, 'log transformed image ', temp_image) return temp_image def getTransformOffset(self): return self.transform_offset def convert_limits(self, limits): if not self.dimap is None: first_limit = self.dimap.transform(self.transform_offset + limits[0]) second_limit = self.dimap.transform(self.transform_offset + limits[1]) else: first_limit = None second_limit = None return [first_limit, second_limit] def to_QImage(self, image): # convert to 8 bit image image_for_display = None if image.dtype == numpy.complex64 or image.dtype == numpy.complex128: self.complex = True real_array = image.real if self.log_scale: temp_array = self.convert_to_log(real_array) temp_array = self.convert_to_log(real_array) if not self.r_cmin is None and not self.r_cmax is None: limits = self.convert_limits([self.r_cmin,self.r_cmax]) else: limits = [self.r_cmin, self.r_cmax] byte_image = bytescale(temp_array,limits) else: limits = [self.r_cmin,self.r_cmax] byte_image = bytescale(real_array,limits) (nx,ny) = real_array.shape image_for_display = numpy.empty(shape=(nx*2,ny),dtype=byte_image.dtype); image_for_display[:nx,:] = byte_image imag_array = image.imag if self.log_scale: temp_array = self.convert_to_log(imag_array) if not self.i_cmin is None and not self.i_cmax is None: limits = self.convert_limits([self.i_cmin,self.i_cmax]) else: limits = [self.i_cmin, self.i_cmax] byte_image = bytescale(temp_array,limits) else: limits = [self.i_cmin,self.i_cmax] byte_image = bytescale(imag_array,limits) image_for_display[nx:,:] = byte_image else: if self.log_scale: temp_array = self.convert_to_log(image) if not self.r_cmin is None and not self.r_cmax is None: limits = self.convert_limits([self.r_cmin,self.r_cmax]) else: limits = [self.r_cmin, self.r_cmax] #print 'to_QImage log limits = ', limits image_for_display = bytescale(temp_array,limits) else: limits = [self.r_cmin,self.r_cmax] #print 'to_QImage real limits = ', limits image_for_display = bytescale(image,limits) # turn image into a QImage, and return result if not self._nan_flags_array is None: if self.complex: image_for_display[:nx,:] = numpy.where(self._nan_flags_array,1,image_for_display[:nx,:]) image_for_display[nx:,:] = numpy.where(self._nan_flags_array,1,image_for_display[nx:,:]) else: image_for_display = numpy.where(self._nan_flags_array,1,image_for_display) self._image_for_display = image_for_display result = convertToQImage(image_for_display,True).mirrored(0, 1) # always suppress NaNs if not self._nan_flags_array is None: result.setColor(1, qRgb(self.nan_colour, self.nan_colour, self.nan_colour)) return result def toGrayScale(self, Qimage): for i in range(0, 256): Qimage.setColor(i, qRgb(i, i, i)) def toHippo(self, Qimage): dv = 255.0 vmin = 1.0 for i in range(2, 256): r = 1.0 g = 1.0 b = 1.0 v = 1.0 * i if (v < (vmin + 0.25 * dv)): r = 0; if dv != 0: g = 4 * (v - vmin) / dv; elif (v < (vmin + 0.5 * dv)): r = 0; if dv != 0: b = 1 + 4 * (vmin + 0.25 * dv - v) / dv; elif (v < (vmin + 0.75 * dv)): b = 0; if dv != 0: r = 4 * (v - vmin - 0.5 * dv) / dv; else: b = 0; if dv != 0: g = 1 + 4 * (vmin + 0.75 * dv - v) / dv; else: r = 0 red = int ( r * 255. ) green = int ( g * 255. ) blue = int ( b * 255. ) # the following call will use the previous computations to # set up a hippo-like color display Qimage.setColor(i, qRgb(red, green, blue)) def setImage(self, image): # convert to QImage self.Qimage = self.to_QImage(image) # set color scale a la HippoDraw Scale if self.display_type == "hippo": self.toHippo(self.Qimage) # set color scale to Grayscale if self.display_type == "grayscale": self.toGrayScale(self.Qimage) # compute flagged image if required if not self._flags_array is None or not self._nan_flags_array is None: self.setFlagQimage() def setFlagQimage(self): (nx,ny) = self._image_for_display.shape image_for_display = self._image_for_display.copy() if not self._flags_array is None: if self.complex: image_for_display[:nx//2,:] = numpy.where(self._flags_array,0,self._image_for_display[:nx//2,:]) image_for_display[nx//2:,:] = numpy.where(self._flags_array,0,self._image_for_display[nx//2:,:]) else: image_for_display = numpy.where(self._flags_array,0,self._image_for_display) if not self._nan_flags_array is None: if self.complex: image_for_display[:nx//2,:] = numpy.where(self._nan_flags_array,1,image_for_display[:nx//2,:]) image_for_display[nx//2:,:] = numpy.where(self._nan_flags_array,1,image_for_display[nx//2:,:]) else: image_for_display = numpy.where(self._nan_flags_array,1,image_for_display) self.flags_Qimage = convertToQImage(image_for_display,True).mirrored(0, 1) # set color scale a la HippoDraw Scale if self.display_type == "hippo": self.toHippo(self.flags_Qimage) # set color scale to Grayscale if self.display_type == "grayscale": self.toGrayScale(self.flags_Qimage) # set zero to black to display flag image pixels in black self.flags_Qimage.setColor(0, qRgb(self.flag_colour, self.flag_colour, self.flag_colour)) self.flags_Qimage.setColor(1, qRgb(self.nan_colour, self.nan_colour, self.nan_colour)) def setBrentjensImage(self, image): absmin = abs(image.min()) MaxAbs = abs(image.max()) if (absmin > MaxAbs): MaxAbs = absmin self.ValueAxis.calcTransferFunction(-MaxAbs, MaxAbs, 0, self.ComplexColorMap.getNumberOfColors()-1) if image.min() != image.max(): # get real and imaginary arrays real_image = image.real imag_image = image.imag shape = image.shape Ncol = self.ComplexColorMap.getNumberOfColors() bits_per_pixel = 32 self.Qimage = QImage(shape[0], shape[1], bits_per_pixel, Ncol) for i in range(shape[0]): for j in range(shape[1]): colre = int(self.ValueAxis.worldToAxis(real_image[i,j])) colim = int(self.ValueAxis.worldToAxis(imag_image[i,j])) if(colre < Ncol and colim < Ncol): value = self.ComplexColorMap.get_color_value(colre,colim) self.Qimage.setPixel(i,j,value) else: if HAS_TIMBA: _dprint(2, "*************************************"); _dprint(2, "colre: ", colre); _dprint(2, "colim: ", colim); _dprint(2, "real : ", real_image[i,j]); _dprint(2, "imag : ", imag_image[i,j]); _dprint(2, "Ncol: ", Ncol); _dprint(2, "*************************************"); self.Qimage.mirror(0,1) def setData(self, data_array, xScale = None, yScale = None): '*** setting data **** ' self.complex = False shape = data_array.shape if HAS_TIMBA: _dprint(3, 'array shape is ', shape) shape0 = shape[0] if data_array.dtype == numpy.complex64 or data_array.dtype == numpy.complex128: self.complex = True shape0 = 2 * shape[0] if xScale: self.xMap = ImageScaler(0, shape0 - 1, xScale[0], xScale[1]) self.xMap_draw = ImageScaler(0, shape0 - 1, xScale[0], xScale[1]) temp_scale = (xScale[0],xScale[1]) self.plot.setAxisScale(QwtPlot.xBottom, *temp_scale) if HAS_TIMBA: _dprint(3, 'xScale is ', xScale) else: self.xMap = ImageScaler(0, shape0, 0, shape0 ) self.xMap_draw = ImageScaler(0, shape0, 0, shape0 ) self.plot.setAxisScale(QwtPlot.xBottom, 0, shape0) if yScale: if HAS_TIMBA: _dprint(3, 'yScale is ', yScale) _dprint(3, 'self.log_y_scale is ', self.log_y_scale) self.yMap = ImageScaler(0, shape[1]-1, yScale[0], yScale[1],self.log_y_scale) self.yMap_draw = ImageScaler(0, shape[1]-1, yScale[0], yScale[1],self.log_y_scale) temp_scale = (yScale[0],yScale[1]) if HAS_TIMBA: _dprint(3, 'Called setAxisScale(QwtPlot.yLeft) with ', temp_scale) self.plot.setAxisScale(QwtPlot.yLeft, *temp_scale) else: self.yMap = ImageScaler(0, shape[1], 0, shape[1]) self.yMap_draw = ImageScaler(0, shape[1], 0, shape[1]) self.plot.setAxisScale(QwtPlot.yLeft, 0, shape[1]) self.setImage(data_array) self.raw_image = data_array # setData() def update_yMap_draw(self, d1, d2): self.yMap_draw.setDblRange(d1, d2, self.log_y_scale) def update_xMap_draw(self, d1, d2): self.xMap_draw.setDblRange(d1, d2, self.log_y_scale) def get_xMap_draw_coords(self): return (self.xMap_draw.d1(), self.xMap_draw.d2()) def get_yMap_draw_coords(self): return (self.yMap_draw.d1(), self.yMap_draw.d2()) def draw(self, painter, xMap, yMap,rect): """Paint image to zooming to xMap, yMap Calculate (x1, y1, x2, y2) so that it contains at least 1 pixel, and copy the visible region to scale it to the canvas. NOTE: we ignore the system-provided Maps and use our own 'draw' maps """ if self.Qimage is None: return # #_dprint(3, 'incoming x map ranges ',xMap.s1(), ' ', xMap.s2()) # #_dprint(3, 'incoming y map ranges ',yMap.s1(), ' ', yMap.s2()) # print 'incoming x map ranges ',xMap.s1(), ' ', xMap.s2() # print 'incoming y map ranges ',yMap.s1(), ' ', yMap.s2() # print 'incoming x map draw ranges ',self.xMap_draw.d1(), ' ', self.xMap_draw.d2() # print 'incoming y map draw ranges ',self.yMap_draw.d1(), ' ', self.yMap_draw.d2() # print 'incoming self x map ranges ',self.xMap.d1(), ' ', self.xMap.d2() # print 'incoming self y map ranges ',self.yMap.d1(), ' ', self.yMap.d2() # calculate y1, y2 y1 = y2 = self.Qimage.height() if HAS_TIMBA: _dprint(3, 'image height ', self.Qimage.height()) # y1 = y2 = self.Qimage.height() - 1 # print 'starting image height ', y1 y1 *= (self.yMap.d2() - self.yMap_draw.d2()) y1 /= (self.yMap.d2() - self.yMap.d1()) # y1 = max(0, int(y1-0.5)) y1 = max(0, (y1-0.5)) if HAS_TIMBA: _dprint(3, 'float y1 ', y1) y1 = int(y1 + 0.5) y2 *= (self.yMap.d2() - self.yMap_draw.d1()) y2 /= (self.yMap.d2() - self.yMap.d1()) if HAS_TIMBA: _dprint(3, 'float y2 ', y2) # y2 = min(self.Qimage.height(), int(y2+0.5)) y2 = min(self.Qimage.height(), (y2+0.5)) y2 = int(y2) if HAS_TIMBA: _dprint(3, 'int y1, y2 ', y1, ' ',y2) # calculate x1, x2 - these are OK x1 = x2 = self.Qimage.width() # print 'starting image width ', x1 x1 *= (xMap.s1() - self.xMap.d1()) x1 /= (self.xMap.d2() - self.xMap.d1()) if HAS_TIMBA: _dprint(3, 'float x1 ', x1) # x1 = max(0, int(x1-0.5)) x1 = max(0, int(x1)) x2 *= (self.xMap_draw.d2() - self.xMap.d1()) x2 /= (self.xMap.d2() - self.xMap.d1()) if HAS_TIMBA: _dprint(3, 'float x2 ', x2) x2 = min(self.Qimage.width(), int(x2+0.5)) if HAS_TIMBA: _dprint(3, 'int x1, x2 ', x1, ' ',x2) # copy xdelta = x2-x1 ydelta = y2-y1 # print 'xdelta ydelta ', xdelta, ' ', ydelta # print 'x1 y1 ', x1, ' ', y1 # these tests seem necessary for the dummy 'scalar' displays if xdelta < 0: xdelta = self.Qimage.height() if ydelta < 0: ydelta = self.Qimage.height() image = None if self._display_flags: image = self.flags_Qimage.copy(x1, y1, xdelta, ydelta) else: image = self.Qimage.copy(x1, y1, xdelta, ydelta) # zoom image = image.scaled(xMap.p2()-xMap.p1()+1, yMap.p1()-yMap.p2()+1) # draw painter.drawImage(xMap.p1(), yMap.p2(), image)