merged with inp

This commit is contained in:
Luke Campagnola 2013-03-19 16:05:32 -04:00
commit f029e7893e
7 changed files with 171 additions and 30 deletions

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
"""
Demonstrates a way to put multiple axes around a single plot.
(This will eventually become a built-in feature of PlotItem)
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
pw.setWindowTitle('pyqtgraph example: MultiplePlotAxes')
p1 = pw.plotItem
p1.setLabels(left='axis 1')
## create a new ViewBox, link the right axis to its coordinate system
p2 = pg.ViewBox()
p1.showAxis('right')
p1.scene().addItem(p2)
p1.getAxis('right').linkToView(p2)
p2.setXLink(p1)
p1.getAxis('right').setLabel('axis2', color='#0000ff')
## create third ViewBox.
## this time we need to create a new axis as well.
p3 = pg.ViewBox()
ax3 = pg.AxisItem('right')
p1.layout.addItem(ax3, 2, 3)
p1.scene().addItem(p3)
ax3.linkToView(p3)
p3.setXLink(p1)
ax3.setZValue(-10000)
ax3.setLabel('axis 3', color='#ff0000')
## Handle view resizing
def updateViews():
## view has resized; update auxiliary views to match
global p1, p2, p3
p2.setGeometry(p1.vb.sceneBoundingRect())
p3.setGeometry(p1.vb.sceneBoundingRect())
## need to re-update linked axes since this was called
## incorrectly while views had different shapes.
## (probably this should be handled in ViewBox.resizeEvent)
p2.linkedViewChanged(p1.vb, p2.XAxis)
p3.linkedViewChanged(p1.vb, p3.XAxis)
updateViews()
p1.vb.sigResized.connect(updateViews)
p1.plot([1,2,4,8,16,32])
p2.addItem(pg.PlotCurveItem([10,20,40,80,40,20], pen='b'))
p3.addItem(pg.PlotCurveItem([3200,1600,800,400,200,100], pen='r'))
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -56,7 +56,7 @@ rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))
rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9))) rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9)))
rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9))) rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9)))
#rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9))) #rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9)))
#rois.append(pg.PolyLineROI([[110, 60], [20, 30], [50, 10]], pen=(6,9))) rois.append(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True))
def update(roi): def update(roi):
img1b.setImage(roi.getArrayRegion(arr, img1a), levels=(0, arr.max())) img1b.setImage(roi.getArrayRegion(arr, img1a), levels=(0, arr.max()))

View File

@ -314,10 +314,13 @@ class AxisItem(GraphicsWidget):
view.sigResized.connect(self.linkedViewChanged) view.sigResized.connect(self.linkedViewChanged)
def linkedViewChanged(self, view, newRange=None): def linkedViewChanged(self, view, newRange=None):
if self.orientation in ['right', 'left'] and view.yInverted(): if self.orientation in ['right', 'left']:
if newRange is None: if newRange is None:
newRange = view.viewRange()[1] newRange = view.viewRange()[1]
self.setRange(*newRange[::-1]) if view.yInverted():
self.setRange(*newRange[::-1])
else:
self.setRange(*newRange)
else: else:
if newRange is None: if newRange is None:
newRange = view.viewRange()[0] newRange = view.viewRange()[0]
@ -330,18 +333,12 @@ class AxisItem(GraphicsWidget):
## extend rect if ticks go in negative direction ## extend rect if ticks go in negative direction
## also extend to account for text that flows past the edges ## also extend to account for text that flows past the edges
if self.orientation == 'left': if self.orientation == 'left':
#rect.setRight(rect.right() - min(0,self.tickLength))
#rect.setTop(rect.top() - 15)
#rect.setBottom(rect.bottom() + 15)
rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15) rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15)
elif self.orientation == 'right': elif self.orientation == 'right':
#rect.setLeft(rect.left() + min(0,self.tickLength))
rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15) rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15)
elif self.orientation == 'top': elif self.orientation == 'top':
#rect.setBottom(rect.bottom() - min(0,self.tickLength))
rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength)) rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength))
elif self.orientation == 'bottom': elif self.orientation == 'bottom':
#rect.setTop(rect.top() + min(0,self.tickLength))
rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0) rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0)
return rect return rect
else: else:

View File

@ -249,7 +249,7 @@ class ImageItem(GraphicsObject):
def render(self): def render(self):
prof = debug.Profiler('ImageItem.render', disabled=True) prof = debug.Profiler('ImageItem.render', disabled=True)
if self.image is None: if self.image is None or self.image.size == 0:
return return
if isinstance(self.lut, collections.Callable): if isinstance(self.lut, collections.Callable):
lut = self.lut(self.image) lut = self.lut(self.image)
@ -269,6 +269,8 @@ class ImageItem(GraphicsObject):
return return
if self.qimage is None: if self.qimage is None:
self.render() self.render()
if self.qimage is None:
return
prof.mark('render QImage') prof.mark('render QImage')
if self.paintMode is not None: if self.paintMode is not None:
p.setCompositionMode(self.paintMode) p.setCompositionMode(self.paintMode)

View File

@ -112,7 +112,10 @@ class PlotCurveItem(GraphicsObject):
if orthoRange is not None: if orthoRange is not None:
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
d = d[mask] d = d[mask]
d2 = d2[mask] #d2 = d2[mask]
if len(d) == 0:
return (None, None)
## Get min/max (or percentiles) of the requested data range ## Get min/max (or percentiles) of the requested data range
if frac >= 1.0: if frac >= 1.0:

View File

@ -802,7 +802,11 @@ class ROI(GraphicsObject):
Also returns the transform which maps the ROI into data coordinates. Also returns the transform which maps the ROI into data coordinates.
If returnSlice is set to False, the function returns a pair of tuples with the values that would have If returnSlice is set to False, the function returns a pair of tuples with the values that would have
been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))""" been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))
If the slice can not be computed (usually because the scene/transforms are not properly
constructed yet), then the method returns None.
"""
#print "getArraySlice" #print "getArraySlice"
## Determine shape of array along ROI axes ## Determine shape of array along ROI axes
@ -810,8 +814,11 @@ class ROI(GraphicsObject):
#print " dshape", dShape #print " dshape", dShape
## Determine transform that maps ROI bounding box to image coordinates ## Determine transform that maps ROI bounding box to image coordinates
tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) try:
tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform())
except np.linalg.linalg.LinAlgError:
return None
## Modify transform to scale from image coords to data coords ## Modify transform to scale from image coords to data coords
#m = QtGui.QTransform() #m = QtGui.QTransform()
tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
@ -1737,11 +1744,34 @@ class PolyLineROI(ROI):
def shape(self): def shape(self):
p = QtGui.QPainterPath() p = QtGui.QPainterPath()
if len(self.handles) == 0:
return p
p.moveTo(self.handles[0]['item'].pos()) p.moveTo(self.handles[0]['item'].pos())
for i in range(len(self.handles)): for i in range(len(self.handles)):
p.lineTo(self.handles[i]['item'].pos()) p.lineTo(self.handles[i]['item'].pos())
p.lineTo(self.handles[0]['item'].pos()) p.lineTo(self.handles[0]['item'].pos())
return p return p
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
sl = self.getArraySlice(data, img, axes=(0,1))
if sl is None:
return None
sliced = data[sl[0]]
im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
im.fill(0x0)
p = QtGui.QPainter(im)
p.setPen(fn.mkPen(None))
p.setBrush(fn.mkBrush('w'))
p.setTransform(self.itemTransform(img)[0])
bounds = self.mapRectToItem(img, self.boundingRect())
p.translate(-bounds.left(), -bounds.top())
p.drawPath(self.shape())
p.end()
mask = fn.imageToArray(im)[:,:,0].astype(float) / 255.
shape = [1] * data.ndim
shape[axes[0]] = sliced.shape[axes[0]]
shape[axes[1]] = sliced.shape[axes[1]]
return sliced * mask
class LineSegmentROI(ROI): class LineSegmentROI(ROI):
@ -1845,8 +1875,8 @@ class SpiralROI(ROI):
#for h in self.handles: #for h in self.handles:
#h['pos'] = h['item'].pos()/self.state['size'][0] #h['pos'] = h['item'].pos()/self.state['size'][0]
def stateChanged(self): def stateChanged(self, finish=True):
ROI.stateChanged(self) ROI.stateChanged(self, finish=finish)
if len(self.handles) > 1: if len(self.handles) > 1:
self.path = QtGui.QPainterPath() self.path = QtGui.QPainterPath()
h0 = Point(self.handles[0]['item'].pos()).length() h0 = Point(self.handles[0]['item'].pos()).length()

View File

@ -467,12 +467,32 @@ class ViewBox(GraphicsWidget):
padding = 0.02 padding = 0.02
return padding return padding
def scaleBy(self, s, center=None): def scaleBy(self, s=None, center=None, x=None, y=None):
""" """
Scale by *s* around given center point (or center of view). Scale by *s* around given center point (or center of view).
*s* may be a Point or tuple (x, y) *s* may be a Point or tuple (x, y).
Optionally, x or y may be specified individually. This allows the other
axis to be left unaffected (note that using a scale factor of 1.0 may
cause slight changes due to floating-point error).
""" """
scale = Point(s) if s is not None:
scale = Point(s)
else:
scale = [x, y]
affect = [True, True]
if scale[0] is None and scale[1] is None:
return
elif scale[0] is None:
affect[0] = False
scale[0] = 1.0
elif scale[1] is None:
affect[1] = False
scale[1] = 1.0
scale = Point(scale)
if self.state['aspectLocked'] is not False: if self.state['aspectLocked'] is not False:
scale[0] = self.state['aspectLocked'] * scale[1] scale[0] = self.state['aspectLocked'] * scale[1]
@ -481,21 +501,37 @@ class ViewBox(GraphicsWidget):
center = Point(vr.center()) center = Point(vr.center())
else: else:
center = Point(center) center = Point(center)
tl = center + (vr.topLeft()-center) * scale tl = center + (vr.topLeft()-center) * scale
br = center + (vr.bottomRight()-center) * scale br = center + (vr.bottomRight()-center) * scale
self.setRange(QtCore.QRectF(tl, br), padding=0)
def translateBy(self, t): if not affect[0]:
self.setYRange(tl.y(), br.y(), padding=0)
elif not affect[1]:
self.setXRange(tl.x(), br.x(), padding=0)
else:
self.setRange(QtCore.QRectF(tl, br), padding=0)
def translateBy(self, t=None, x=None, y=None):
""" """
Translate the view by *t*, which may be a Point or tuple (x, y). Translate the view by *t*, which may be a Point or tuple (x, y).
"""
t = Point(t)
#if viewCoords: ## scale from pixels
#o = self.mapToView(Point(0,0))
#t = self.mapToView(t) - o
Alternately, x or y may be specified independently, leaving the other
axis unchanged (note that using a translation of 0 may still cause
small changes due to floating-point error).
"""
vr = self.targetRect() vr = self.targetRect()
self.setRange(vr.translated(t), padding=0) if t is not None:
t = Point(t)
self.setRange(vr.translated(t), padding=0)
elif x is not None:
x1, x2 = vr.left()+x, vr.right()+x
self.setXRange(x1, x2, padding=0)
elif y is not None:
y1, y2 = vr.top()+y, vr.bottom()+y
self.setYRange(y1, y2, padding=0)
def enableAutoRange(self, axis=None, enable=True): def enableAutoRange(self, axis=None, enable=True):
""" """
@ -935,7 +971,10 @@ class ViewBox(GraphicsWidget):
else: else:
tr = dif*mask tr = dif*mask
tr = self.mapToView(tr) - self.mapToView(Point(0,0)) tr = self.mapToView(tr) - self.mapToView(Point(0,0))
self.translateBy(tr) x = tr.x() if mask[0] == 1 else None
y = tr.y() if mask[1] == 1 else None
self.translateBy(x=x, y=y)
self.sigRangeChangedManually.emit(self.state['mouseEnabled']) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
elif ev.button() & QtCore.Qt.RightButton: elif ev.button() & QtCore.Qt.RightButton:
#print "vb.rightDrag" #print "vb.rightDrag"
@ -950,8 +989,11 @@ class ViewBox(GraphicsWidget):
tr = self.childGroup.transform() tr = self.childGroup.transform()
tr = fn.invertQTransform(tr) tr = fn.invertQTransform(tr)
x = s[0] if mask[0] == 1 else None
y = s[1] if mask[1] == 1 else None
center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton)))
self.scaleBy(s, center) self.scaleBy(x=x, y=y, center=center)
self.sigRangeChangedManually.emit(self.state['mouseEnabled']) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
def keyPressEvent(self, ev): def keyPressEvent(self, ev):