3

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
Victoria Price
  • 637
  • 3
  • 13
  • 26

2 Answers2

6

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.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • Thank you so much, this is a phenomenal help! I wasn't even sure where to start, to be honest. I added the code that I came up with for generating the data and image from the binary file; what I'm stuck on is how to generate the data (and therefore the image) for a new frame each time the slider is moved... – Victoria Price Jul 02 '12 at 18:27
  • @VictoriaPrice: I don't really understand your code too much. How do you select different "images" via your code? If you show me an example of how you do that, I will update my answer with an example of how to connect them. – jdi Jul 02 '12 at 18:57
  • Sorry! I'm a super-beginner with all of this (no coding experience whatsoever prior to about a month ago). I start with binary files from a sonar "camera". I read in the files and separate the file header information, and the frame header information and each string of data into numpy arrays. Then, I use the data from the Frame class numpy array to create a 0-255 bitmap image with the Echogram class. Right now, I'm saving the image (mostly just to ensure that it works), but eventually I need to iterate through each frame in the file and create an image. Does that help? – Victoria Price Jul 02 '12 at 19:10
  • Unfortunately, not much. All I really wanted to know was a simple couple lines of how you pull QPixmap frames on demand. Its a lot of code to stare at and try to make sense of. Can you update your question with a simple example in a few lines of how to use it and pull a few QPixmaps? – jdi Jul 02 '12 at 19:18
  • Also, its hard for me to follow the code because of how it is structured. I totally appreciate that you are just learning. If you want to have a conversation about this more in depth, we can start a chat. – jdi Jul 02 '12 at 20:39
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/13370/discussion-between-jdi-and-victoria-price) – jdi Jul 03 '12 at 15:19
2

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.

Luke
  • 11,374
  • 2
  • 48
  • 61
  • Thank you! I'll spend some time exploring this today-- looks like this could be exactly what I need. – Victoria Price Jul 03 '12 at 14:06
  • Hi Luke, I'm having a hard time getting pyqtgraph to work... I get a NameError: name asUnicode not defined type error when I try to import it. Any ideas? – Victoria Price Jul 11 '12 at 17:30
  • Hrmp. I haven't seen that before. Can you tell me what revision you downloaded as well as your OS and python version? (It might be best to discuss this on the mailing list: https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph ) – Luke Jul 12 '12 at 02:28