Pyqtgraph: Overloaded AxisItem only showing original data - pyqt

I'm building an application to view real time data based on the examples for scrolling plots. My x-axis should display time as a formated string. The x-values added to the plot are timestamp floats in seconds. Here is a simplified version of my plot window code.
Everything works in real time and I have no problem showing the values i want to plot, but the labels for my x-axis are only the timestamps not the formated strings. I know that the function formatTimestampToString(val) in the AxisItem overload returns a good string value.
import pyqtgraph as pg
class NewLegend(pg.LegendItem):
def __init__(self, size=None, offset=None):
pg.LegendItem.__init__(self, size, offset)
def paint(self, p, *args):
p.setPen(pg.mkPen(0,0,0)) # outline
p.setBrush(pg.mkBrush(255,255,255)) # background
p.drawRect(self.boundingRect())
class DateAxis(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
strings = []
for val in values:
strings.append(formatTimestampToString(val))
return strings
class PlotWindow(QtWidgets.QWidget):
def __init__(self, iof, num):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Plot Window ')
self.resize(1000, 800)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
"""
... other stuff ...
"""
# Externally updated dict with data
self.data = {}
self.curves = {}
self.plotWidget = pg.GraphicsWindow("Graph Window")
axis = DateAxis(orientation='bottom')
self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis})
self.plot.setAutoPan(x=True)
self.legend = NewLegend(size=(100, 60), offset=(70, 30))
self.legend.setParentItem(self.plot.graphicsItem())
def updatePlots(self):
#update data for the different curves
for data_name, curve in self.curves.items():
if curve != 0:
curve.setData(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:])
def addCurve(self, data_name):
if data_name not in self.curves:
self.curves[data_name] = self.plot.plot(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:], pen=pg.mkPen(color=self.randomColor(),width=3))
self.legend.addItem(self.curves[data_name], name=data_name)
def removeCurve(self, data_name):
if data_name in self.curves:
self.plot.removeItem(self.curves[data_name])
self.legend.removeItem(data_name)
del self.curves[data_name]
Am i doing something wrong? Is there a better way to overload the AxisItem? I have also tried to overload the AxisItem with a simple numerical formula just to see if it has any effect on my axis.
Another problem i have is with the LegendItem: I can add and subtract labels with no problem, but it only updates the size of the legend box when adding labels. This means that when I add and remove curves/data in my plot the legend grows, but never shrinks down again. I have tried calling the LegendItem.updateSize() function after removing labels, but nothing happens.
I hope you can help! Thanks

So I found the problem. In self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis}) its supposed to say axisItems, not axisItem.

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.

How to link axes of all plots in a Bokeh layout?

I am designing a Bokeh layout using the Bokeh server. I am defining two main columns (see attached image), and I am attempting to link the x-axis of all plots on the right column. The problems are that:
I am trying to make this app as dynamic as possible, which mean that depending on the case-study, not all the plots will be available, and each individual plot is set from a separate function
Each plot object is stored in a list, and I don't know how to access their properties
The reference plot is not known a priori so I don't see how I can implement the example in the Bokeh doc - in other words, I need to first plot all the subplots to then get the relevant x_range
So I was wondering if it is possible to set the linking behaviour a posteriori once all plots in the column are defined (i.e. the output of plotDataset below). My intuition is to loop through the objects, get the children and set the x_range to the first plot, but I don't know how to do that.
Below is a simplified version of what I am trying to achieve. Ideally, I would get the x_range of the first plot of fCol and apply it to all other plots just before return column(fCol)
Any idea is greatly appreciated! And also, I am fairly beginner with Python so please shout if you see anything else horrible!
Thank you
def plotTS(data, col):
tTmp = []
# A loop that defines each tab of the plot
for i in range(len(col)):
fTmp = figure()
fTmp.circle(data[:]['time'], data[:][col[i]], color=color)
# Append tab
tTmp.append(Panel(child=fTmp))
# Return the tabs
return Tabs(tabs=tTmp)
def plotDataset(data):
col = ['NDVI', 'EVI'] # Name of the tabs
fCol = []
fCol.append(plotTS(data, col))
# NOTE: I use an append approach because in reality plotTS is called more than once
return column(fCol)
# General layout - I did not include the code for the left column
layout = row(leftColumn, plotDataset(data))
Link to image
See code below (Bokeh v1.1.0).
from bokeh.models import Panel, Tabs, Column, Row
from bokeh.plotting import figure
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
def modify_doc(doc):
leftColumn = Column(figure())
def plotTS(data, col):
tTmp = []
for i in col:
fTmp = figure()
fTmp.circle(data['x'], data['y'], color='black')
tTmp.append(Panel(child=fTmp, title = i))
return Tabs(tabs=tTmp)
def plotDataset(data):
col = ['NDVI', 'EVI']
fCol = plotTS(data, col)
shared_range = None
for panel in fCol.tabs:
fig = panel.child
if shared_range is None:
shared_range = fig.x_range
else:
fig.x_range = shared_range
return Column(fCol)
layout = Row(leftColumn, plotDataset(data = dict(x = [1, 2, 3], y = [1, 2, 3])))
doc.add_root(layout)
io_loop = IOLoop.current()
server = Server(applications = {'/app': Application(FunctionHandler(modify_doc))}, io_loop = io_loop, port = 5002)
server.start()
server.show('/app')
io_loop.start()

how to set axis interval in candle stick using pyqtgraph with pyqt5?

I am using pyqtgraph to draw a candle stick like the following:
#-------------------
# Create pyqtgraph module
#-------------------
def GUI_DRAW_create():
"""
set default config
"""
pg.setConfigOption('background', 'w') #background: white
pg.setConfigOption('foreground', 'k') #font: black
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value) for value in values]
## Create a subclass of GraphicsObject.
## The only required methods are paint() and boundingRect()
## (see QGraphicsItem documentation)
class CandlestickItem(pg.GraphicsObject):
def __init__(self, data):
pg.GraphicsObject.__init__(self)
self.data = data ## data must have fields: time, open, close, min, max
self.generatePicture()
def generatePicture(self):
## pre-computing a QPicture object allows paint() to run much more quickly,
## rather than re-drawing the shapes every time.
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('k'))
w = (self.data[1][0] - self.data[0][0]) / 3.
for (t, open, close, min, max) in self.data:
p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
if open > close:
p.setBrush(pg.mkBrush('r'))
else:
p.setBrush(pg.mkBrush('g'))
p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open))
p.end()
# I try to print out t here, is my date
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
## boundingRect _must_ indicate the entire area that will be drawn on
## or else we will get artifacts and possibly crashing.
## (in this case, QPicture does all the work of computing the bouning rect for us)
return QtCore.QRectF(self.picture.boundingRect())
class GUI_DRAW_new(QMainWindow):
def __init__(self):
super().__init__()
GUI_DRAW_create()
self.setWindowTitle("pyqtgraph example: PlotWidget")
cw = QWidget()
self.setCentralWidget(cw)
main_layout = QHBoxLayout()
cw.setLayout(main_layout)
#variable
self.signalgraph = None
self.data = []
self.vb = None
self.vLine = None
# define plot windows
self.GUI_DRAW_new_graph()
main_layout.addWidget(self.signalgraph)
self.signalgraph.setMouseTracking(True)
self.signalgraph.viewport().installEventFilter(self)
self.show()
def eventFilter(self, source, event):
try:
if (event.type() == QtCore.QEvent.MouseMove and
source is self.signalgraph.viewport()):
pos = event.pos()
print('mouse move: (%d, %d)' % (pos.x(), pos.y()))
if self.signalgraph.sceneBoundingRect().contains(pos):
mousePoint = self.vb.mapSceneToView(pos)
index = int(mousePoint.x())
int(index)
#if index > 0 and index < len(self.data):
#print(self.xdict[index])
# self.label.setHtml("<p style='color:black'>日期:{0}</p>".format(self.data[index]))
# self.label.setPos(mousePoint.x(),mousePoint.y())
self.vLine.setPos(mousePoint.x())
return QtGui.QWidget.eventFilter(self, source, event)
except Exception as e:
traceback.print_exc()
err = sys.exc_info()[1]
PRINT_DEBUG(0,str(err))
def GUI_DRAW_new_graph(self):
try:
self.signalgraph = pg.PlotWidget(name="Signalgraph", axisItems={'bottom': TimeAxisItem(orientation='bottom')})
# sample data
self.data = [ ## fields are (time, open, close, min, max).
(1514995200.0, 102.610001, 105.349998, 102, 105.370003),
(1515081600.0, 105.75, 102.709999, 102.410004, 105.849998),
(1515168000.0, 100.559998, 102.370003, 99.870003, 100.699997),
(1515254400.0, 98.68, 96.449997, 96.43, 100.129997),
(1515340800.0, 98.550003, 96.959999, 96.760002, 99.110001),
(1515427200.0, 102.610001, 105.349998, 102, 105.370003),
(1515513600.0, 105.75, 102.709999, 102.410004, 105.849998),
(1515600000.0, 100.559998, 102.370003, 99.870003, 100.699997),
(1515686400.0, 98.68, 96.449997, 96.43, 100.129997),
(1515772800.0, 98.550003, 96.959999, 96.760002, 99.110001),
]
#if comment this 2 code, can see the string
item = CandlestickItem(self.data)
self.signalgraph.addItem(item)
#trick
s_day = datetime.fromtimestamp(self.data[0][0]).strftime("%Y-%m-%d")
e_day = datetime.fromtimestamp(self.data[len(self.data) - 1][0]).strftime("%Y-%m-%d")
tr=np.arange(s_day, e_day, dtype='datetime64') # tick labels one day
tday0=(tr-tr[0])/(tr[-1]-tr[0]) #Map time to 0.0-1.0 day 2 1.0-2.0 ...
tday1=tday0+1
tnorm=np.concatenate([tday0,tday1])
tr[-1]=tr[0] # End day=start next day
ttick=list()
for i,t in enumerate(np.concatenate([tr,tr])):
tstr=np.datetime64(t).astype(datetime)
ttick.append( (tnorm[i], tstr.strftime("%Y-%m-%d")))
ax=self.signalgraph.getAxis('bottom') #This is the trick
ax.setTicks([ttick])
#cross hair in signalgraph
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.signalgraph.addItem(self.vLine, ignoreBounds=True)
self.vb = self.signalgraph.plotItem.vb
except Exception as e:
traceback.print_exc()
err = sys.exc_info()[1]
print(0,str(err))
# Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == "__main__":
app = QtGui.QApplication([])
gui = GUI_DRAW_new()
app.exec_()
The result is:
candlestick_graph_center
I want to set the x-axis interval using the date: [2018-01-04, 2018-01-05, 2018-01-06, ....].
Thanks for help, many thanks
Update
I figure out that why all data squeeze together, because of the TextItem.
So that, I rewrite the code.
I try to use tickStrings in AxisItem to convert value to string, when I print out the values in tickStrings, it is not my data value (date in timestamp). Why the values are different? Thanks a lots
Update
If I use setTicks with candle stick, it cannot show string in the graph. Only can show the string without candle stick.
Any idea?
You have to convert into a string with the appropriate format using strftime():
from datetime import datetime
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value).strftime("%Y-%m-%d") for value in values]
Or:
from datetime import date
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [date.fromtimestamp(value) for value in values]
Or:
from datetime import datetime
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value).date() for value in values]

python 3 find in list

I am creating a game and unable to detect turtle position in this list
I am using python3 and turtle.
the objective of this code is to create a filled shape when turtle intersect with its's own path
import turtle
t=turtle.Turtle()
t.fillcolor("red")
t.begin_fill()
s=turtle.Screen()
t.penup()
status=True
penstatus=False
t.speed(1)
x=[]
def go1():
t.left(-(t.heading()))
t.left(90)
def go2():
t.left(-(t.heading()))
# t.left(90)
def go3():
t.left(-(t.heading()))
t.left(270)
def go4():
t.left(-(t.heading()))
t.left(180)
def paint():
global penstatus
if penstatus==False:
penstatus=True
t.down()
else:
t.up()
def detect():
global status
a=t.position()
if a in x:
status=False
print("yes")
while status:
global x
s.onkeypress(go1, "Up")
s.onkeypress(go2, "Right")
s.onkeypress(go3, "Down")
s.onkeypress(go4, "Left")
s.onkeypress(paint,"space")
s.listen()
x.append(t.position())
t.fd(5)
detect()
t.end_fill()
s.mainloop()
it works sometimes but result of filling also gets wrong
There are two reasons you're having trouble detecting if the current position is in your list of past positions. The first is you "hop" five pixels at a time so you are potentially crossing the line at a "filled in" segment, not one you were actually positioned on.
The second is that turtle positions are floating point numbers and can be very slightly different when you come back to the same spot. We can fix both problems by not comparing directly but asking if the distance between points is less than our "hop" distance.
My rework of your code below implements this approach. It also changes how your keys work slightly; changes the logic to only include visible lines in the filled graphic; and is completely event-based. It also has a reset, "r", key to start a new drawing. You can back out any changes you don't like, the back position detection is still applicable:
from turtle import Turtle, Screen
DISTANCE = 3
def go_up():
turtle.setheading(90)
def go_right():
turtle.setheading(0)
def go_down():
turtle.setheading(270)
def go_left():
turtle.setheading(180)
def toggle_pen():
if turtle.isdown():
turtle.penup()
else:
turtle.pendown()
turtle.begin_fill() # ignore pending begin_fill, start anew
def reset_drawing():
global positions
turtle.reset()
turtle.fillcolor('red')
turtle.speed('fastest')
turtle.penup()
positions = []
move()
def move():
for position in positions:
if turtle.distance(position) < DISTANCE:
turtle.end_fill()
return
if turtle.isdown():
positions.append(turtle.position())
turtle.forward(DISTANCE)
screen.ontimer(move, 100)
screen = Screen()
screen.onkeypress(go_up, 'Up')
screen.onkeypress(go_right, 'Right')
screen.onkeypress(go_down, 'Down')
screen.onkeypress(go_left, 'Left')
screen.onkeypress(toggle_pen, 'space')
screen.onkeypress(reset_drawing, 'r')
screen.listen()
turtle = Turtle()
positions = None # make sure global is defined
reset_drawing()
screen.mainloop()

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