Connecting slider to Graphics View in PyQt
-
18-06-2021 - |
Frage
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
Lösung
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.
Andere Tipps
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:
(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.