A QGraphicsPixmapItem with a transparency gradient - python-3.x

I would like to apply a transparency gradient on a QGraphicsPixmapItem but I don't know how to go about it, for now I can only apply the transparency on the whole QGraphicsPixmapItem (not the gradient). The item undergoes a mirror effect (like an element on a wall which is reflected on the ground), then I would like to set up this gradient of transparency (from top to bottom; quite opaque at the top ... and in several stages of transparencies, it ends up being completely transparent at the bottom of the item), then I add blur. This is the part of the transparency gradient that I can't seem to do... I don't understand how it can work. Everything I've tried doesn't work. Can you help me ?
Thanks in advance.
Here is a fairly short code:
for item in self.scene.selectedItems() :
# Width
item_height = item.boundingRect().height()
# mirroir
item.setTransform(QTransform(1, 0, 0, 0, -1, 0, 0, 0, 1))
#
item.setOpacity(0.7)
# Blur
blur = QGraphicsBlurEffect()
blur.setBlurRadius(8)
item.setGraphicsEffect(blur)
item.setPos(100, 100 + 2 * (int(item_height)))
##############################################
"""
alphaGradient = QLinearGradient(item.boundingRect().topLeft(), item.boundingRect().bottomLeft())
alphaGradient.setColorAt(0.0, Qt.transparent)
alphaGradient.setColorAt(0.5, Qt.black)
alphaGradient.setColorAt(1.0, Qt.transparent)
effect = QGraphicsOpacityEffect()
effect.setOpacityMask(alphaGradient)
item.setGraphicsEffect(effect) ########
"""
##############################################
"""
opacity = QGraphicsOpacityEffect()
lg = QLinearGradient()
lg.setStart(0, 0)
lg.setFinalStop(0, 100)
lg.setColorAt(0, Qt.transparent)
lg.setColorAt(0.5, Qt.transparent)
lg.setColorAt(1.0, Qt.transparent)
opacity.setOpacityMask(lg)
#opacity.setOpacity(1)
item.setGraphicsEffect(opacity)
"""
##############################################

Only one single QGraphicsEffect can be applied for each target item (or widget), so you have two options:
create a new image that applies the opacity gradient (using QPainter composition modes) and only set the blur effect;
set the opacity effect on a parent item;
In both cases, I'd suggest you to create the "mirror" item as a child of the actual QGraphicsPixmapItem, which will ensure that it will always follow what happens to the parent, including visibility and geometry changes, transformations, etc.
Generate the mirror image with a transparency gradient
For this, we use the composition features of QPainter, and specifically the CompositionMode_SourceIn which allows to use a gradient as source for the alpha channel of the image that is being drawn.
The procedure is the following:
create a new image based on the size of the mirrored one;
create a gradient that has full opacity at 0 and is transparent at 1 (or less);
draw that gradient on the image;
set the composition mode;
draw the mirrored image;
create a QGraphicsPixmapItem based on that image;
class MirrorPixmapItem(QtWidgets.QGraphicsPixmapItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mirror = QtWidgets.QGraphicsPixmapItem(self)
self.blurEffect = QtWidgets.QGraphicsBlurEffect(blurRadius=8)
self.mirror.setGraphicsEffect(self.blurEffect)
if not self.pixmap().isNull():
self.createMirror()
def setPixmap(self, pixmap):
old = self.pixmap()
super().setPixmap(pixmap)
if old == pixmap:
return
self.createMirror()
def setOffset(self, *args):
super().setOffset(*args)
if not self.mirror.pixmap().isNull():
self.mirror.setOffset(self.offset())
def createMirror(self):
source = self.pixmap()
if source.isNull():
self.mirror.setPixmap(source)
return
scale = .5
height = int(source.height() * scale)
output = QtGui.QPixmap(source.width(), height)
output.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(output)
grad = QtGui.QLinearGradient(0, 0, 0, height)
grad.setColorAt(0, QtCore.Qt.black)
grad.setColorAt(1, QtCore.Qt.transparent)
qp.fillRect(output.rect(), grad)
qp.setCompositionMode(qp.CompositionMode_SourceIn)
qp.drawPixmap(0, 0,
source.transformed(QtGui.QTransform().scale(1, -scale)))
qp.end()
self.mirror.setPixmap(output)
self.mirror.setY(self.boundingRect().bottom())
self.mirror.setOffset(self.offset())
Set the opacity on the parent item
In this case, we still have the blur effect on the mirror item, but we apply a QGraphicsOpacityEffect on the parent (the original image) and use the opacityMask that includes both the original image and the mirror, will be at full opacity down to the height of the original, and then begins to fade out to full transparency for the mirror.
class MirrorPixmapItem2(QtWidgets.QGraphicsPixmapItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.opacity = QtWidgets.QGraphicsOpacityEffect(opacity=1)
self.setGraphicsEffect(self.opacity)
self.mirror = QtWidgets.QGraphicsPixmapItem(self)
self.blurEffect = QtWidgets.QGraphicsBlurEffect(blurRadius=8)
self.mirror.setGraphicsEffect(self.blurEffect)
if not self.pixmap().isNull():
self.createMirror()
# ... override setPixmap() and setOffset() as above
def createMirror(self):
source = self.pixmap()
if source.isNull():
self.mirror.setPixmap(source)
return
scale = .5
self.mirror.setPixmap(
source.transformed(QtGui.QTransform().scale(1, -scale)))
self.mirror.setPos(self.boundingRect().bottomLeft())
self.mirror.setOffset(self.offset())
height = source.height()
gradHeight = height + height * scale
grad = QtGui.QLinearGradient(0, 0, 0, gradHeight)
grad.setColorAt(height / gradHeight, QtCore.Qt.black)
grad.setColorAt(1, QtCore.Qt.transparent)
self.opacity.setOpacityMask(grad)
This is the result, as you can see there's no visible difference, which approach to use is a matter of choice, but the first one might be slightly better from the perspective of performance, as no additional effect is required when the items are drawn, which might be important if you are going to have many (as in hundreds or thousands) image items and/or at high resolutions.

Related

How to aviod rubberband become a a line and How to set rubberband only show border line?

I write a simple app, While drag or scale the MainView, The PartView rubberband will show scene area in PartView.But sometime the rubber-band become a line, and sometime the rubberband disappear.So How to aviod this phenomenon appear?And sometime I want the rubberband only show it's border-line, not contain it's light-blue rectangle,So how can I write code ?
My Code
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import random
import math
r = lambda : random.randint(0, 255)
r255 = lambda : (r(), r(), r())
class Scene(QGraphicsScene):
def __init__(self):
super().__init__()
for i in range(1000):
item = QGraphicsEllipseItem()
item.setRect(0, 0, r(), r())
item.setBrush(QColor(*r255()))
item.setPos(r()*100, r()*100)
self.addItem(item)
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
super().drawBackground(painter, rect)
self.sigExposeRect.emit(rect)
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
def __init__(self):
super().__init__()
self.r = QRubberBand(QRubberBand.Rectangle, self)
self.r.setWindowOpacity(1)
self.r.show()
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.m.sigExposeRect.connect(self.onExposeRect)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
def onExposeRect(self, rect: QRectF):
prect = self.p.mapFromScene(rect).boundingRect()
self.p.r.setGeometry(prect)
app = QApplication([])
v = View()
v.show()
app.exec()
My Result
I think the problem is that the qrect passed to the drawBackground method is only includes the portion of the background that wasn't previously in the viewport. Not positive about that though.
Either way I was able to achieve your goal of avoiding only a section of the rubber band being drawn, by sending the area for the entire viewport to the onExposeRect slot.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
# Adding this next line was the only change I made
orect = self.mapToScene(self.viewport().geometry()).boundingRect()
super().drawBackground(painter, rect)
self.sigExposeRect.emit(orect) # and passing it to the slot.
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
A fundamental aspect about Graphics View is its high performance in drawing even thousands of elements.
To achieve this, one of the most important optimization is updating only the portions of the scene that really need redrawing, similar to what item views do, as they normally only redraw the items that actually require updates, instead of always painting the whole visible area, which can be a huge bottleneck.
This is the reason for which overriding drawBackground is ineffective: sometimes, only a small portion of the scene is updated (and, in certain situations, even no update is done at all), and the rect argument of drawBackground only includes that portion, not the whole visible area. The result is that in these situations, the signal will emit a rectangle that will not be consistent with the visible area.
Since the visible area is relative to the viewport of the scroll area, the only safe way to receive updates about that area is to connect to the horizontal and vertical scroll bars (which always work even if they are hidden).
A further precaution is to ensure that the visible rectangle is also updated whenever the scene rect is changed (since that change might not be reflected by the scroll bars), by connecting to the sceneRectChanged signal and also overriding the setSceneRect() of the source view. Considering that the changes in vertical and scroll bars might coincide, it's usually a good idea to delay the signal with a 0-delay QTimer, so that it's only sent once when more changes to the visible area happen at the same time.
Note that since you're not actually using the features of QRubberBand, there's little use in its usage, especially if you also need custom painting. Also, since the rubber band is a child of the view, it will always keep its position even if the preview view is scrolled.
In the following example I'll show two ways of drawing the "fake" rubber band (but choose only one of them, either comment one or the other to test them) that will always be consistent with both the source and target views.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signalDelay = QTimer(self, singleShot=True, interval=0,
timeout=self.emitExposeRect)
# signals might have arguments that collide with the start(interval)
# override of QTimer, let's use a basic lambda that ignores them
self.delayEmit = lambda *args: self.signalDelay.start()
self.verticalScrollBar().valueChanged.connect(self.delayEmit)
self.horizontalScrollBar().valueChanged.connect(self.delayEmit)
def emitExposeRect(self):
topLeft = self.mapToScene(self.viewport().geometry().topLeft())
bottomRight = self.mapToScene(self.viewport().geometry().bottomRight())
self.sigExposeRect.emit(QRectF(topLeft, bottomRight))
def setScene(self, scene):
if self.scene() == scene:
return
if self.scene():
try:
self.scene().sceneRectChanged.disconnect(self.delayEmit)
except TypeError:
pass
super().setScene(scene)
if scene:
scene.sceneRectChanged.connect(self.delayEmit)
def setSceneRect(self, rect):
super().setSceneRect(rect)
self.delayEmit()
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
exposeRect = None
def updateExposeRect(self, rect):
if self.exposeRect != rect:
self.exposeRect = rect
self.viewport().update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.exposeRect:
return
rect = self.mapFromScene(self.exposeRect).boundingRect()
# use either *one* of the following:
# 1. QStyle implementation, imitates QRubberBand
qp = QStylePainter(self.viewport())
opt = QStyleOptionRubberBand()
opt.initFrom(self)
opt.rect = rect
qp.drawControl(QStyle.CE_RubberBand, opt)
# 2. basic QPainter
qp = QPainter(self.viewport())
color = self.palette().highlight().color()
qp.setPen(self.palette().highlight().color())
# for background
bgd = QColor(color)
bgd.setAlpha(40)
qp.setBrush(bgd)
qp.drawRect(rect)
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
self.m.sigExposeRect.connect(self.p.updateExposeRect)
PS: please use single letter variables when they actually make sense (common variables, coordinates, loop placeholders, etc.), not for complex objects, and especially for attributes: there's no benefit in using self.m or self.p, and the only result you get is to make code less readable to you and others.

Python factory, where object parameters can be specified gradually?

Assuming we have the following object (in reality many more parameters):
class Room:
def __init__(self, h, w, color):
self.h = h
self.w = w
self.color = color
print(f'Room is {self.h}*{self.w} and painted in {self.color}')
Room(0, 1, 'red') # Room is 0*1 and painted in red
Further assume we need to create this object hundreds of times. A few of the parameters need to be changed each time. However, most of the parameters stay consistent within one "session". Therefore, I would like to fix those parameters at the beginning of my session, to make the subsequent calls much simpler.
My current approach looks like this. Surely there is something better out there?
class RoomFactory:
def __init__(self, h=None, w=None, color=None):
self.h = h
self.w = w
self.color = color
# TODO: Get arguments from Room.__init__ automatically?
def create(self, **kwargs):
kwargs_already_set = {k: v for k, v in self.__dict__.items() if v}
return Room(**kwargs_already_set, **kwargs)
# Define some parameters at session beginning. Which parameters depends on session!
rf = RoomFactory()
rf.h = 5
rf.w = 5
# Later, create hundreds of objects with different remaining arguments
rf.create(color='green') # Room is 5*5 and painted in green
rf.create(color='black') # Room is 5*5 and painted in black

Is noLoop stopping execution of draw?

This is my first post here, so I apologize if I'm making any mistakes.
I recently started to study Processing in Python mode and I'm trying to develop a code that, after selecting an image from your computer, reads the colors and inserts them in a list. The final idea is to calculate the percentage of certain colors in the image. For this I am using the following code:
img = None
tam=5
cores_img = []
def setup():
size (500, 500)
selectInput(u"Escolha a ilustração para leitura de cores", "adicionar_imagens")
noLoop()
def adicionar_imagens(selection):
global img
if selection == None:
print(u"Seleção cancelada")
else:
print(u"Você selecionou " + selection.getAbsolutePath())
img = loadImage(selection.getAbsolutePath())
def draw():
if img is not None:
image (img, 0, 0)
for xg in range(0, img.width, tam):
x = map(xg, 0, img.width, 0, img.width)
for yg in range(0, img.height, tam):
y = map(yg, 0, img.height, 0, img.height)
cor = img.get(int(x), int(y))
cores_img.append(cor)
print (cores_img)
I'm using noLoop() so that the colors are added only once to the list. However, it seems that the draw is not running. It performs the setup actions, but when the image is selected, nothing happens. There is also no error message.
I'm completely lost about what might be happening. If anyone has any ideas and can help, I really appreciate it!
Calling noLoop() indeed stops the draw() loop from running, which means by the time you've selected and image nothing yould happen.
You can however manually call draw() (or redraw()) once the image is loaded:
img = None
tam=5
cores_img = []
def setup():
size (500, 500)
selectInput(u"Escolha a ilustração para leitura de cores", "adicionar_imagens")
noLoop()
def adicionar_imagens(selection):
global img
if selection == None:
print(u"Seleção cancelada")
else:
print(u"Você selecionou " + selection.getAbsolutePath())
img = loadImage(selection.getAbsolutePath())
redraw()
def draw():
if img is not None:
image (img, 0, 0)
for xg in range(0, img.width, tam):
x = map(xg, 0, img.width, 0, img.width)
for yg in range(0, img.height, tam):
y = map(yg, 0, img.height, 0, img.height)
cor = img.get(int(x), int(y))
cores_img.append(cor)
print (cores_img)
You should pay attention to a few details:
As the reference mentions, calling get() is slow: pixels[x + y * width] is faster (just remember to call loadPixels() if the array doesn't look right)
PImage already has a pixels array: calling img.resize(img.width / tam, img .height / tam) should downsample the image so you can read the same list
x = map(xg, 0, img.width, 0, img.width) (and similarly y) maps from one range to the same range which has no effect
e.g.
img = None
tam=5
cores_img = None
def setup():
size (500, 500)
selectInput(u"Escolha a ilustração para leitura de cores", "adicionar_imagens")
noLoop()
def adicionar_imagens(selection):
global img, cores_img
if selection == None:
print(u"Seleção cancelada")
else:
print(u"Você selecionou " + selection.getAbsolutePath())
img = loadImage(selection.getAbsolutePath())
print("total pixels",len(img.pixels))
img.resize(img.width / tam, img.height / tam);
cores_img = list(img.pixels)
print("resized pixels",len(img.pixels))
print(cores_img)
def draw():
pass
Update
I thought that calling noLoop on setup would make draw run once. Still
it won't print the image... I'm calling 'image (img, 0, 0)' at the end
of 'else', on 'def adicionar_imagens (selection)'. Should I call it
somewhere else?
think of adicionar_imagens time-wise, running separate to setup() and draw()
you are right, draw() should be called once (because of noLoop()), however it's called as soon as setup() completes but not later (as navigating the file system, selecting a file and confirming takes time)
draw() would need to be forced to run again after the image was loaded
Here's an updated snippet:
img = None
# optional: potentially useful for debugging
img_resized = None
tam=5
cores_img = None
def setup():
size (500, 500)
selectInput(u"Escolha a ilustração para leitura de cores", "adicionar_imagens")
noLoop()
def adicionar_imagens(selection):
global img, img_resized, cores_img
if selection == None:
print(u"Seleção cancelada")
else:
print(u"Você selecionou " + selection.getAbsolutePath())
img = loadImage(selection.getAbsolutePath())
# make a copy of the original image (to keep it intact)
img_resized = img.get()
# resize
img_resized.resize(img.width / tam, img.height / tam)
# convert pixels array to python list
cores_img = list(img.pixels)
# force redraw
redraw()
# print data
print("total pixels",len(img.pixels))
print("resized pixels",len(img.pixels))
# print(cores_img)
def draw():
print("draw called " + str(millis()) + "ms after sketch started")
# if an img was selected and loaded, display it
if(img != None):
image(img, 0, 0)
# optionally display resized image
if(img_resized != None):
image(img_resized, 0, 0)
Here are a couple of notes that may be helpful:
each pixel in the list is a 24 bit ARGB colour (e.g. all channels are stored in a single value). if you need individual colour channels remember you have functions like red(), green(), blue() available. (Also if that gets slow notice the example include faster versions using bit shifting and masking)
the Histogram example could be helpful. You would need to port from Java to Python syntax and use 3 histograms (one for each colour channel), however the principle of counting intensities is nicely illustrated

How do I put the origin of the canvas in the center

I am trying to write simple turtle graphics for my Transcrypt Python to JavaScript compiler. To be compatible with the CPython Turtle module, I need carthesian coordinates, i.e. the origin must be in the center of the canvas. Is there any way I can achieve that without using a separate coordinate system myself and transforming them for each object in my own code.
I've seen that for individual objects I can use centerX and centerY to center their reference point inside such object. But I couldn't find an example of putting the origin in the middle of the canvas.
The solution given in How to change the origin of the canvas to the center? works, but looks somewhat artificial, as I would to compensate the mouse position for this translation. Also it gives a strange "doubling" of some line segments, probably due to an offset. If a translate the coordinates in my own code, the doubling is absent. Both translations add integers.
Translation using canvas context, some lines double. Code:
class Turtle:
def __init__ (self):
self._canvas = __new__ (fabric.Canvas ('canvas', {'backgroundColor': 'lightgray'}))
self._canvas.onWindowResize = self.resize
self._canvas.onWindowDraw = self.draw
self._context = self._canvas.getContext ('2d')
self._context.translate (self._canvas.getWidth () / 2, self._canvas.getHeight () / 2)
self.reset ()
def reset (self):
self._canvas.clear ()
self.pensize (1)
self.color ('black')
self.down ()
self._position = [0, 0]
self._heading = Math.PI / 2
Translation in my application code, as it should look.
Code:
class Turtle:
def __init__ (self):
self._canvas = __new__ (fabric.Canvas ('canvas', {'backgroundColor': 'lightgray'}))
self._canvas.onWindowResize = self.resize
self._canvas.onWindowDraw = self.draw
self._context = self._canvas.getContext ('2d')
self.reset ()
def reset (self):
self._canvas.clear ()
self.pensize (1)
self.color ('black')
self.down ()
self._position = [self._canvas.getWidth () / 2, self._canvas.getHeight () / 2]
self._heading = Math.PI / 2
To avoid de "doubling" effect you can try this:
self._context.translate (round(self._canvas.getWidth () / 2), round(self._canvas.getHeight () / 2))

Connecting slider to Graphics View in PyQt

I'm trying to display image data read in from a binary file (I have the code written for retrieving this data from a file and storing it as an image for use with QImage() ). What I would like to do is connect a slider to a Graphics View widget so that when you move the slider, it moves through the frames and displays the image from that frame (these are echograms ranging from 1-500 frames in length). I'm very new to PyQt and was curious how one might even begin doing this?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import numpy as np
class FileHeader(object):
fileheader_fields= ("filetype","fileversion","numframes","framerate","resolution","numbeams","samplerate","samplesperchannel","receivergain","windowstart","winlengthsindex","reverse","serialnumber","date","idstring","ID1","ID2","ID3","ID4","framestart","frameend","timelapse","recordInterval","radioseconds","frameinterval","userassigned")
fileheader_formats=('S3','B','i4','i4','i4','i4','f','i4','i4','i4','i4','i4','i4','S32','S256','i4','i4','i4','i4','i4','i4','i4','i4','i4','i4','S136')
def __init__(self,filename,parent=None):
a=QApplication([])
filename=str(QFileDialog.getOpenFileName(None,"open file","C:/vprice/DIDSON/DIDSON Data","*.ddf"))
self.infile=open(filename, 'rb')
dtype=dict(names=self.fileheader_fields, formats=self.fileheader_formats)
self.fileheader=np.fromfile(self.infile, dtype=dtype, count=1)
self.fileheader_length=self.infile.tell()
for field in self.fileheader_fields:
setattr(self,field,self.fileheader[field])
def get_frame_first(self):
frame=Frame(self.infile)
print self.fileheader
self.infile.seek(self.fileheader_length)
print frame.frameheader
print frame.data
def __iter__(self):
self.infile.seek(self.fileheader_length)
for _ in range(self.numframes):
yield Frame(self.infile)
#def close(self):
#self.infile.close()
def display(self):
print self.fileheader
class Frame(object):
frameheader_fields=("framenumber","frametime","version","status","year","month","day","hour","minute","second","hsecond","transmit","windowstart","index","threshold","intensity","receivergain","degc1","degc2","humidity","focus","battery","status1","status2","velocity","depth","altitude","pitch","pitchrate","roll","rollrate","heading","headingrate","sonarpan","sonartilt","sonarroll","latitude","longitude","sonarposition","configflags","userassigned")
frameheader_formats=("i4","2i4","S4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","i4","S16","S16","f","f","f","f","f","f","f","f","f","f","f","f","f8","f8","f","i4","S60")
data_format="uint8"
def __init__(self,infile):
dtype=dict(names=self.frameheader_fields,formats=self.frameheader_formats)
self.frameheader=np.fromfile(infile,dtype=dtype,count=1)
for field in self.frameheader_fields:
setattr(self,field,self.frameheader[field])
ncols,nrows=96,512
self.data=np.fromfile(infile,self.data_format,count=ncols*nrows)
self.data=self.data.reshape((nrows,ncols))
class QEchogram():
def __init__(self):
self.__colorTable=[]
self.colorTable=None
self.threshold=[50,255]
self.painter=None
self.image=None
def echogram(self):
fileheader=FileHeader(self)
frame=Frame(fileheader.infile)
echoData=frame.data
#fileName = fileName
self.size=[echoData.shape[0],echoData.shape[1]]
# define the size of the data (and resulting image)
#size = [96, 512]
# create a color table for our image
# first define the colors as RGB triplets
colorTable = [(255,255,255),
(159,159,159),
(95,95,95),
(0,0,255),
(0,0,127),
(0,191,0),
(0,127,0),
(255,255,0),
(255,127,0),
(255,0,191),
(255,0,0),
(166,83,60),
(120,60,40),
(200,200,200)]
# then create a color table for Qt - this encodes the color table
# into a list of 32bit integers (4 bytes) where each byte is the
# red, green, blue and alpha 8 bit values. In this case we don't
# set alpha so it defaults to 255 (opaque)
ctLength = len(colorTable)
self.__ctLength=ctLength
__colorTable = []
for c in colorTable:
__colorTable.append(QColor(c[0],c[1],c[2]).rgb())
echoData = np.round((echoData - self.threshold[0])*(float(self.__ctLength)/(self.threshold[1]-self.threshold[0])))
echoData[echoData < 0] = 0
echoData[echoData > self.__ctLength-1] = self.__ctLength-1
echoData = echoData.astype(np.uint8)
self.data=echoData
# create an image from our numpy data
image = QImage(echoData.data, echoData.shape[1], echoData.shape[0], echoData.shape[1],
QImage.Format_Indexed8)
image.setColorTable(__colorTable)
# convert to ARGB
image = image.convertToFormat(QImage.Format_ARGB32)
# save the image to file
image.save(fileName)
self.image=QImage(self.size[0],self.size[1],QImage.Format_ARGB32)
self.painter=QPainter(self.image)
self.painter.drawImage(QRect(0.0,0.0,self.size[0],self.size[1]),image)
def getImage(self):
self.painter.end()
return self.image
def getPixmap(self):
self.painter.end()
return QPixmap.fromImage(self.image)
if __name__=="__main__":
data=QEchogram()
fileName="horizontal.png"
data.echogram()
dataH=data.data
print "Horizontal data", dataH
I could give you a more specific answer if you showed what you were trying so far, but for now I will just make assumptions and give you an example.
First what you would do is create a QSlider. You set the QSlider minimum/maximum to the range of images that you have available. When you slide it, the sliderMoved signal will fire and tell you what the new value is.
Next, you can create a list containing all of your QPixmap images ahead of time. If these images are huge and you are concerned about memory, you might have to create them on demand using your already coded approach. But we will assume you can put them in a list for now, to make the example easier.
Then you create your QGraphics set up, using a single QGraphicsPixmapItem. This item can have its pixmap replaced on demand.
Putting it all together, you get something like this:
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.resize(640,480)
self.layout = QtGui.QVBoxLayout(self)
self.scene = QtGui.QGraphicsScene(self)
self.view = QtGui.QGraphicsView(self.scene)
self.layout.addWidget(self.view)
self.image = QtGui.QGraphicsPixmapItem()
self.scene.addItem(self.image)
self.view.centerOn(self.image)
self._images = [
QtGui.QPixmap('Smiley.png'),
QtGui.QPixmap('Smiley2.png')
]
self.slider = QtGui.QSlider(self)
self.slider.setOrientation(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
# max is the last index of the image list
self.slider.setMaximum(len(self._images)-1)
self.layout.addWidget(self.slider)
# set it to the first image, if you want.
self.sliderMoved(0)
self.slider.sliderMoved.connect(self.sliderMoved)
def sliderMoved(self, val):
print "Slider moved to:", val
try:
self.image.setPixmap(self._images[val])
except IndexError:
print "Error: No image at index", val
if __name__ == "__main__":
app = QtGui.QApplication([])
w = Widget()
w.show()
w.raise_()
app.exec_()
You can see that we set the range of the slider to match your image list. At any time, you can change this range if the contents of your image list change. When the sliderMoved fires, it will use the value as the index of the image list and set the pixmap.
I also added a check to our sliderMoved() SLOT just in case your slider range gets out of sync with your image list. If you slide to an index that doesn't exist in your image list, it will fail gracefully and leave the existing image.
A lot of the work you are doing--converting image data to QImage, displaying frames with a slider--might be solved better using a library written for this purpose. There are a couple libraries I can think of that work with PyQt and provide everything you need:
guiqwt
pyqtgraph
(disclaimer: shameless plug)
If you can collect all of the image data into a single 3D numpy array, the code for displaying this in pyqtgraph looks like:
import pyqtgraph as pg
pg.image(imageData)
This would give you a zoomable image display with frame slider and color lookup table controls.

Resources