documentation updates

This commit is contained in:
Luke Campagnola 2012-04-18 00:02:15 -04:00
parent 44f2a0ecc4
commit 4eadccdcc1
14 changed files with 409 additions and 188 deletions

View File

@ -23,36 +23,37 @@ class GraphicsScene(QtGui.QGraphicsScene):
events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent
is private)
- Generates MouseClicked events in addition to the usual press/move/release events.
* Generates MouseClicked events in addition to the usual press/move/release events.
(This works around a problem where it is impossible to have one item respond to a
drag if another is watching for a click.)
- Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
- Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
- Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
* Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
* Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
This lets us indicate unambiguously to the user which item they are about to click/drag on
- Eats mouseMove events that occur too soon after a mouse press.
- Reimplements items() and itemAt() to circumvent PyQt bug
* Eats mouseMove events that occur too soon after a mouse press.
* Reimplements items() and itemAt() to circumvent PyQt bug
Mouse interaction is as follows:
1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents
as well as custom HoverEvents.
2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button),
acceptDrags(button) or both. If this method call returns True, this informs the item that _if_
the user clicks/drags the specified mouse button, the item is guaranteed to be the
recipient of click/drag events (the item may wish to change its appearance to indicate this).
If the call to acceptClicks/Drags returns False, then the item is guaranteed to NOT receive
If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive
the requested event (because another item has already accepted it).
3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then
No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows
items to function properly if they are expecting the usual press/move/release sequence of events.
(It is recommended that items do NOT accept press events, and instead use click/drag events)
Note: The default implementation of QGraphicsItem.mousePressEvent will ACCEPT the event if the
Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the
item is has its Selectable or Movable flags enabled. You may need to override this behavior.
3) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events.
4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events.
If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released,
then a mouseDragEvent is generated.
If no drag events are generated before the button is released, then a mouseClickEvent is generated.
4) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent
5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent
in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event.
ClickEvents may be delivered in this way even if no
item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial
@ -470,23 +471,25 @@ class GraphicsScene(QtGui.QGraphicsScene):
The final menu will look like:
Original Item 1
Original Item 2
...
Original Item N
------------------
Parent Item 1
Parent Item 2
...
Grandparent Item 1
...
| Original Item 1
| Original Item 2
| ...
| Original Item N
| ------------------
| Parent Item 1
| Parent Item 2
| ...
| Grandparent Item 1
| ...
Arguments:
item - The item that initially created the context menu
(This is probably the item making the call to this function)
menu - The context menu being shown by the item
event - The original event that triggered the menu to appear.
============== ==================================================
**Arguments:**
item The item that initially created the context menu
(This is probably the item making the call to this function)
menu The context menu being shown by the item
event The original event that triggered the menu to appear.
============== ==================================================
"""
#items = self.itemsNearEvent(ev)

View File

@ -4,6 +4,13 @@ import weakref
import pyqtgraph.ptime as ptime
class MouseDragEvent:
"""
Instances of this class are delivered to items in a :class:`GraphicsScene <pyqtgraph.GraphicsScene>` via their mouseDragEvent() method when the item is being mouse-dragged.
"""
def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False):
self.start = start
self.finish = finish
@ -27,59 +34,99 @@ class MouseDragEvent:
self._modifiers = moveEvent.modifiers()
def accept(self):
"""An item should call this method if it can handle the event. This will prevent the event being delivered to any other items."""
self.accepted = True
self.acceptedItem = self.currentItem
def ignore(self):
"""An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items."""
self.accepted = False
def isAccepted(self):
return self.accepted
def scenePos(self):
"""Return the current scene position of the mouse."""
return Point(self._scenePos)
def screenPos(self):
"""Return the current screen position (pixels relative to widget) of the mouse."""
return Point(self._screenPos)
def buttonDownScenePos(self, btn=None):
"""
Return the scene position of the mouse at the time *btn* was pressed.
If *btn* is omitted, then the button that initiated the drag is assumed.
"""
if btn is None:
btn = self.button()
return Point(self._buttonDownScenePos[int(btn)])
def buttonDownScreenPos(self, btn=None):
"""
Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed.
If *btn* is omitted, then the button that initiated the drag is assumed.
"""
if btn is None:
btn = self.button()
return Point(self._buttonDownScreenPos[int(btn)])
def lastScenePos(self):
"""
Return the scene position of the mouse immediately prior to this event.
"""
return Point(self._lastScenePos)
def lastScreenPos(self):
"""
Return the screen position of the mouse immediately prior to this event.
"""
return Point(self._lastScreenPos)
def buttons(self):
"""
Return the buttons currently pressed on the mouse.
(see QGraphicsSceneMouseEvent::buttons in the Qt documentation)
"""
return self._buttons
def button(self):
"""Return the button that initiated the drag (may be different from the buttons currently pressed)"""
"""Return the button that initiated the drag (may be different from the buttons currently pressed)
(see QGraphicsSceneMouseEvent::button in the Qt documentation)
"""
return self._button
def pos(self):
"""
Return the current position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
"""
Return the previous position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def buttonDownPos(self, btn=None):
"""
Return the position of the mouse at the time the drag was initiated
in the coordinate system of the item that the event was delivered to.
"""
if btn is None:
btn = self.button()
return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)]))
def isStart(self):
"""Returns True if this event is the first since a drag was initiated."""
return self.start
def isFinish(self):
"""Returns False if this is the last event in a drag. Note that this
event will have the same position as the previous one."""
return self.finish
def __repr__(self):
@ -88,11 +135,21 @@ class MouseDragEvent:
return "<MouseDragEvent (%g,%g)->(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish()))
def modifiers(self):
"""Return any keyboard modifiers currently pressed.
(see QGraphicsSceneMouseEvent::modifiers in the Qt documentation)
"""
return self._modifiers
class MouseClickEvent:
"""
Instances of this class are delivered to items in a :class:`GraphicsScene <pyqtgraph.GraphicsScene>` via their mouseClickEvent() method when the item is clicked.
"""
def __init__(self, pressEvent, double=False):
self.accepted = False
self.currentItem = None
@ -106,37 +163,60 @@ class MouseClickEvent:
def accept(self):
"""An item should call this method if it can handle the event. This will prevent the event being delivered to any other items."""
self.accepted = True
self.acceptedItem = self.currentItem
def ignore(self):
"""An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items."""
self.accepted = False
def isAccepted(self):
return self.accepted
def scenePos(self):
"""Return the current scene position of the mouse."""
return Point(self._scenePos)
def screenPos(self):
"""Return the current screen position (pixels relative to widget) of the mouse."""
return Point(self._screenPos)
def buttons(self):
"""
Return the buttons currently pressed on the mouse.
(see QGraphicsSceneMouseEvent::buttons in the Qt documentation)
"""
return self._buttons
def button(self):
"""Return the mouse button that generated the click event.
(see QGraphicsSceneMouseEvent::button in the Qt documentation)
"""
return self._button
def double(self):
"""Return True if this is a double-click."""
return self._double
def pos(self):
"""
Return the current position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
"""
Return the previous position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def modifiers(self):
"""Return any keyboard modifiers currently pressed.
(see QGraphicsSceneMouseEvent::modifiers in the Qt documentation)
"""
return self._modifiers
def __repr__(self):
@ -150,8 +230,9 @@ class MouseClickEvent:
class HoverEvent:
"""
Instances of this class are delivered to items in a :class:`GraphicsScene <pyqtgraph.GraphicsScene>` via their hoverEvent() method when the mouse is hovering over the item.
This event class both informs items that the mouse cursor is nearby and allows items to
communicate with one another about whether each item will accept _potential_ mouse events.
communicate with one another about whether each item will accept *potential* mouse events.
It is common for multiple overlapping items to receive hover events and respond by changing
their appearance. This can be misleading to the user since, in general, only one item will
@ -188,13 +269,21 @@ class HoverEvent:
def isEnter(self):
"""Returns True if the mouse has just entered the item's shape"""
return self.enter
def isExit(self):
"""Returns True if the mouse has just exited the item's shape"""
return self.exit
def acceptClicks(self, button):
""""""
"""Inform the scene that the item (that the event was delivered to)
would accept a mouse click event if the user were to click before
moving the mouse again.
Returns True if the request is successful, otherwise returns False (indicating
that some other item would receive an incoming click).
"""
if not self.acceptable:
return False
if button not in self.__clickItems:
@ -203,6 +292,13 @@ class HoverEvent:
return False
def acceptDrags(self, button):
"""Inform the scene that the item (that the event was delivered to)
would accept a mouse drag event if the user were to drag before
the next hover event.
Returns True if the request is successful, otherwise returns False (indicating
that some other item would receive an incoming drag event).
"""
if not self.acceptable:
return False
if button not in self.__dragItems:
@ -211,24 +307,40 @@ class HoverEvent:
return False
def scenePos(self):
"""Return the current scene position of the mouse."""
return Point(self._scenePos)
def screenPos(self):
"""Return the current screen position of the mouse."""
return Point(self._screenPos)
def lastScenePos(self):
"""Return the previous scene position of the mouse."""
return Point(self._lastScenePos)
def lastScreenPos(self):
"""Return the previous screen position of the mouse."""
return Point(self._lastScreenPos)
def buttons(self):
"""
Return the buttons currently pressed on the mouse.
(see QGraphicsSceneMouseEvent::buttons in the Qt documentation)
"""
return self._buttons
def pos(self):
"""
Return the current position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
"""
Return the previous position of the mouse in the coordinate system of the item
that the event was delivered to.
"""
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self):
@ -237,6 +349,9 @@ class HoverEvent:
return "<HoverEvent (%g,%g)->(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit()))
def modifiers(self):
"""Return any keyboard modifiers currently pressed.
(see QGraphicsSceneMouseEvent::modifiers in the Qt documentation)
"""
return self._modifiers
def clickItems(self):

View File

@ -119,9 +119,10 @@ QAPP = None
def plot(*args, **kargs):
"""
| Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it.
| Accepts a *title* argument to set the title of the window.
| All other arguments are used to plot data. (see :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`)
Create and return a :class:`PlotWindow <pyqtgraph.PlotWindow>`
(this is just a window with :class:`PlotWidget <pyqtgraph.PlotWidget>` inside), plot data in it.
Accepts a *title* argument to set the title of the window.
All other arguments are used to plot data. (see :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`)
"""
mkQApp()
#if 'title' in kargs:
@ -149,10 +150,11 @@ def plot(*args, **kargs):
def image(*args, **kargs):
"""
| Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside.
| Will show 2D or 3D image data.
| Accepts a *title* argument to set the title of the window.
| All other arguments are used to show data. (see :func:`ImageView.setImage() <pyqtgraph.ImageView.setImage>`)
Create and return an :class:`ImageWindow <pyqtgraph.ImageWindow>`
(this is just a window with :class:`ImageView <pyqtgraph.ImageView>` widget inside), show image data inside.
Will show 2D or 3D image data.
Accepts a *title* argument to set the title of the window.
All other arguments are used to show data. (see :func:`ImageView.setImage() <pyqtgraph.ImageView.setImage>`)
"""
mkQApp()
w = ImageWindow(*args, **kargs)

View File

@ -9,3 +9,4 @@ Contents:
functions
graphicsItems/index
widgets/index
graphicsscene/index

View File

@ -0,0 +1,8 @@
GraphicsScene
=============
.. autoclass:: pyqtgraph.GraphicsScene
:members:
.. automethod:: pyqtgraph.GraphicsScene.__init__

View File

@ -0,0 +1,5 @@
HoverEvent
==========
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.HoverEvent
:members:

View File

@ -0,0 +1,12 @@
GraphicsScene and Mouse Events
==============================
Contents:
.. toctree::
:maxdepth: 2
graphicsscene
hoverevent
mouseclickevent
mousedragevent

View File

@ -0,0 +1,5 @@
MouseClickEvent
===============
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseClickEvent
:members:

View File

@ -0,0 +1,5 @@
MouseDragEvent
==============
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseDragEvent
:members:

View File

@ -17,7 +17,7 @@ Pyqtgraph makes it very easy to visualize data from the command line. Observe::
import pyqtgraph as pg
pg.plot(data) # data can be a list of values or a numpy array
The example above would open a window displaying a line plot of the data given. I don't think it could reasonably be any simpler than that. The call to pg.plot returns a handle to the plot widget that is created, allowing more data to be added to the same window.
The example above would open a window displaying a line plot of the data given. The call to :func:`pg.plot <pyqtgraph.plot>` returns a handle to the :class:`plot widget <pyqtgraph.PlotWidget>` that is created, allowing more data to be added to the same window.
Further examples::
@ -43,5 +43,5 @@ While I consider this approach somewhat lazy, it is often the case that 'lazy' i
Embedding widgets inside PyQt applications
------------------------------------------
For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: PlotWidget, ImageView, GraphicsView, GraphicsLayoutWidget. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality.
For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget <pyqtgraph.PlotWidget>`, :class:`ImageView <pyqtgraph.ImageView>`, :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and :class:`GraphicsView <pyqtgraph.GraphicsView>`. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality.

View File

@ -44,8 +44,8 @@ This will start a launcher with a list of available examples. Select an item fro
How does it compare to...
-------------------------
* matplotlib: For plotting and making publication-quality graphics, matplotlib is far more mature than pyqtgraph. However, matplotlib is also much slower and not suitable for applications requiring realtime update of plots/video or rapid interactivity. It also does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph.
* matplotlib: For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster. Matplotlib is more aimed toward making publication-quality graphics, whereas pyqtgraph is intended for use in data acquisition and analysis applications. Matplotlib is more intuitive for matlab programmers; pyqtgraph is more intuitive for python/qt programmers. Matplotlib (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc.
* pyqwt5: pyqwt is generally more mature than pyqtgraph for plotting and is about as fast. The major differences are 1) pyqtgraph is written in pure python, so it is somewhat more portable than pyqwt, which often lags behind pyqt in development (and can be a pain to install on some platforms) and 2) like matplotlib, pyqwt does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph.
* pyqwt5: About as fast as pyqwt5, but not quite as complete for plotting functionality. Image handling in pyqtgraph is much more complete (again, no ROI widgets in qwt). Also, pyqtgraph is written in pure python, so it is more portable than pyqwt, which often lags behind pyqt in development (I originally used pyqwt, but decided it was too much trouble to rely on it as a dependency in my projects). Like matplotlib, pyqwt (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc.
(My experience with these libraries is somewhat outdated; please correct me if I am wrong here)

View File

@ -780,6 +780,9 @@ class PlotItem(GraphicsWidget):
def removeItem(self, item):
"""
Remove an item from the internal ViewBox.
"""
if not item in self.items:
return
self.items.remove(item)
@ -796,6 +799,9 @@ class PlotItem(GraphicsWidget):
#item.sigPlotChanged.connect(self.plotChanged)
def clear(self):
"""
Remove all items from the ViewBox.
"""
for i in self.items[:]:
self.removeItem(i)
self.avgCurves = {}

View File

@ -37,7 +37,26 @@ class PlotROI(ROI):
class ImageView(QtGui.QWidget):
"""
Widget used for display and analysis of image data.
Implements many features:
* Displays 2D and 3D image data. For 3D data, a z-axis
slider is displayed allowing the user to select which frame is displayed.
* Displays histogram of image data with movable region defining the dark/light levels
* Editable gradient provides a color lookup table
* Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end.
* Basic analysis features including:
* ROI and embedded plot for measuring image values across frames
* Image normalization / background subtraction
Basic Usage::
imv = pg.ImageView()
imv.show()
imv.setImage(data)
"""
sigTimeChanged = QtCore.Signal(object, object)
sigProcessingChanged = QtCore.Signal(object)
@ -149,7 +168,168 @@ class ImageView(QtGui.QWidget):
self.roiClicked() ## initialize roi plot to correct shape / visibility
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None):
"""
Set the image to be displayed in the widget.
============== =======================================================================
**Arguments:**
*img* (numpy array) the image to be displayed.
*xvals* (numpy array) 1D array of z-axis values corresponding to the third axis
in a 3D image. For video, this array should contain the time of each frame.
*autoRange* (bool) whether to scale/pan the view to fit the image.
*autoLevels* (bool) whether to update the white/black levels to fit the image.
*levels* (min, max); the white and black level values to use.
*axes* Dictionary indicating the interpretation for each axis.
This is only needed to override the default guess. Format is::
{'t':0, 'x':1, 'y':2, 'c':3};
============== =======================================================================
"""
prof = debug.Profiler('ImageView.setImage', disabled=True)
if not isinstance(img, np.ndarray):
raise Exception("Image must be specified as ndarray.")
self.image = img
if xvals is not None:
self.tVals = xvals
elif hasattr(img, 'xvals'):
try:
self.tVals = img.xvals(0)
except:
self.tVals = np.arange(img.shape[0])
else:
self.tVals = np.arange(img.shape[0])
#self.ui.timeSlider.setValue(0)
#self.ui.normStartSlider.setValue(0)
#self.ui.timeSlider.setMaximum(img.shape[0]-1)
prof.mark('1')
if axes is None:
if img.ndim == 2:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None}
elif img.ndim == 3:
if img.shape[2] <= 4:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2}
else:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
elif img.ndim == 4:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
else:
raise Exception("Can not interpret image with dimensions %s" % (str(img.shape)))
elif isinstance(axes, dict):
self.axes = axes.copy()
elif isinstance(axes, list) or isinstance(axes, tuple):
self.axes = {}
for i in range(len(axes)):
self.axes[axes[i]] = i
else:
raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes)))
for x in ['t', 'x', 'y', 'c']:
self.axes[x] = self.axes.get(x, None)
prof.mark('2')
self.imageDisp = None
prof.mark('3')
self.currentIndex = 0
self.updateImage()
if levels is None and autoLevels:
self.autoLevels()
if levels is not None: ## this does nothing since getProcessedImage sets these values again.
self.levelMax = levels[1]
self.levelMin = levels[0]
if self.ui.roiBtn.isChecked():
self.roiChanged()
prof.mark('4')
if self.axes['t'] is not None:
#self.ui.roiPlot.show()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
self.timeLine.setValue(0)
#self.ui.roiPlot.setMouseEnabled(False, False)
if len(self.tVals) > 1:
start = self.tVals.min()
stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02
elif len(self.tVals) == 1:
start = self.tVals[0] - 0.5
stop = self.tVals[0] + 0.5
else:
start = 0
stop = 1
for s in [self.timeLine, self.normRgn]:
s.setBounds([start, stop])
#else:
#self.ui.roiPlot.hide()
prof.mark('5')
self.imageItem.resetTransform()
if scale is not None:
self.imageItem.scale(*scale)
if pos is not None:
self.imageItem.setPos(*pos)
prof.mark('6')
if autoRange:
self.autoRange()
self.roiClicked()
prof.mark('7')
prof.finish()
def play(self, rate):
"""Begin automatically stepping frames forward at the given rate (in fps).
This can also be accessed by pressing the spacebar."""
#print "play:", rate
self.playRate = rate
if rate == 0:
self.playTimer.stop()
return
self.lastPlayTime = ptime.time()
if not self.playTimer.isActive():
self.playTimer.start(16)
def autoLevels(self):
"""Set the min/max levels automatically to match the image data."""
#image = self.getProcessedImage()
self.setLevels(self.levelMin, self.levelMax)
#self.ui.histogram.imageChanged(autoLevel=True)
def setLevels(self, min, max):
"""Set the min/max (bright and dark) levels."""
self.ui.histogram.setLevels(min, max)
def autoRange(self):
"""Auto scale and pan the view around the image."""
image = self.getProcessedImage()
#self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True)
self.view.setRange(self.imageItem.boundingRect(), padding=0.)
def getProcessedImage(self):
"""Returns the image data after it has been processed by any normalization options in use."""
if self.imageDisp is None:
image = self.normalize(self.image)
self.imageDisp = image
self.levelMin, self.levelMax = map(float, ImageView.quickMinMax(self.imageDisp))
self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax)
return self.imageDisp
def close(self):
"""Closes the widget nicely, making sure to clear the graphics scene and release memory."""
self.ui.roiPlot.close()
self.ui.graphicsView.close()
#self.ui.gradientWidget.sigGradientChanged.disconnect(self.updateImage)
@ -224,17 +404,6 @@ class ImageView(QtGui.QWidget):
else:
self.play(0)
def play(self, rate):
#print "play:", rate
self.playRate = rate
if rate == 0:
self.playTimer.stop()
return
self.lastPlayTime = ptime.time()
if not self.playTimer.isActive():
self.playTimer.start(16)
def timeout(self):
now = ptime.time()
@ -251,6 +420,7 @@ class ImageView(QtGui.QWidget):
self.jumpFrames(n)
def setCurrentIndex(self, ind):
"""Set the currently displayed frame index."""
self.currentIndex = np.clip(ind, 0, self.getProcessedImage().shape[0]-1)
self.updateImage()
self.ignoreTimeLine = True
@ -258,7 +428,7 @@ class ImageView(QtGui.QWidget):
self.ignoreTimeLine = False
def jumpFrames(self, n):
"""If this is a video, move ahead n frames"""
"""Move video frame ahead n frames (may be negative)"""
if self.axes['t'] is not None:
self.setCurrentIndex(self.currentIndex + n)
@ -360,137 +530,6 @@ class ImageView(QtGui.QWidget):
#self.ui.roiPlot.replot()
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None):
"""Set the image to be displayed in the widget.
Options are:
img: ndarray; the image to be displayed.
autoRange: bool; whether to scale/pan the view to fit the image.
autoLevels: bool; whether to update the white/black levels to fit the image.
levels: (min, max); the white and black level values to use.
axes: {'t':0, 'x':1, 'y':2, 'c':3}; Dictionary indicating the interpretation for each axis.
This is only needed to override the default guess.
"""
prof = debug.Profiler('ImageView.setImage', disabled=True)
if not isinstance(img, np.ndarray):
raise Exception("Image must be specified as ndarray.")
self.image = img
if xvals is not None:
self.tVals = xvals
elif hasattr(img, 'xvals'):
try:
self.tVals = img.xvals(0)
except:
self.tVals = np.arange(img.shape[0])
else:
self.tVals = np.arange(img.shape[0])
#self.ui.timeSlider.setValue(0)
#self.ui.normStartSlider.setValue(0)
#self.ui.timeSlider.setMaximum(img.shape[0]-1)
prof.mark('1')
if axes is None:
if img.ndim == 2:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None}
elif img.ndim == 3:
if img.shape[2] <= 4:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2}
else:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
elif img.ndim == 4:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
else:
raise Exception("Can not interpret image with dimensions %s" % (str(img.shape)))
elif isinstance(axes, dict):
self.axes = axes.copy()
elif isinstance(axes, list) or isinstance(axes, tuple):
self.axes = {}
for i in range(len(axes)):
self.axes[axes[i]] = i
else:
raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes)))
for x in ['t', 'x', 'y', 'c']:
self.axes[x] = self.axes.get(x, None)
prof.mark('2')
self.imageDisp = None
prof.mark('3')
self.currentIndex = 0
self.updateImage()
if levels is None and autoLevels:
self.autoLevels()
if levels is not None: ## this does nothing since getProcessedImage sets these values again.
self.levelMax = levels[1]
self.levelMin = levels[0]
if self.ui.roiBtn.isChecked():
self.roiChanged()
prof.mark('4')
if self.axes['t'] is not None:
#self.ui.roiPlot.show()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
self.timeLine.setValue(0)
#self.ui.roiPlot.setMouseEnabled(False, False)
if len(self.tVals) > 1:
start = self.tVals.min()
stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02
elif len(self.tVals) == 1:
start = self.tVals[0] - 0.5
stop = self.tVals[0] + 0.5
else:
start = 0
stop = 1
for s in [self.timeLine, self.normRgn]:
s.setBounds([start, stop])
#else:
#self.ui.roiPlot.hide()
prof.mark('5')
self.imageItem.resetTransform()
if scale is not None:
self.imageItem.scale(*scale)
if pos is not None:
self.imageItem.setPos(*pos)
prof.mark('6')
if autoRange:
self.autoRange()
self.roiClicked()
prof.mark('7')
prof.finish()
def autoLevels(self):
#image = self.getProcessedImage()
self.setLevels(self.levelMin, self.levelMax)
#self.ui.histogram.imageChanged(autoLevel=True)
def setLevels(self, min, max):
self.ui.histogram.setLevels(min, max)
def autoRange(self):
image = self.getProcessedImage()
#self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True)
self.view.setRange(self.imageItem.boundingRect(), padding=0.)
def getProcessedImage(self):
if self.imageDisp is None:
image = self.normalize(self.image)
self.imageDisp = image
self.levelMin, self.levelMax = map(float, ImageView.quickMinMax(self.imageDisp))
self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax)
return self.imageDisp
@staticmethod
def quickMinMax(data):
@ -578,7 +617,7 @@ class ImageView(QtGui.QWidget):
def timeIndex(self, slider):
"""Return the time and frame index indicated by a slider"""
## Return the time and frame index indicated by a slider
if self.image is None:
return (0,0)
#v = slider.value()

View File

@ -16,11 +16,30 @@ class PlotWidget(GraphicsView):
#sigRangeChanged = QtCore.Signal(object, object) ## already defined in GraphicsView
"""
Widget implementing a graphicsView with a single PlotItem inside.
:class:`GraphicsView <pyqtgraph.GraphicsView>` widget with a single
:class:`PlotItem <pyqtgraph.PlotItem>` inside.
The following methods are wrapped directly from PlotItem: addItem, removeItem,
clear, setXRange, setYRange, setRange, setAspectLocked, setMouseEnabled. For all
other methods, use getPlotItem.
The following methods are wrapped directly from PlotItem:
:func:`addItem <pyqtgraph.PlotItem.addItem>`,
:func:`removeItem <pyqtgraph.PlotItem.removeItem>`,
:func:`clear <pyqtgraph.PlotItem.clear>`,
:func:`setXRange <pyqtgraph.ViewBox.setXRange>`,
:func:`setYRange <pyqtgraph.ViewBox.setYRange>`,
:func:`setRange <pyqtgraph.ViewBox.setRange>`,
:func:`autoRange <pyqtgraph.ViewBox.autoRange>`,
:func:`setXLink <pyqtgraph.ViewBox.setXLink>`,
:func:`setYLink <pyqtgraph.ViewBox.setYLink>`,
:func:`viewRect <pyqtgraph.ViewBox.viewRect>`,
:func:`setMouseEnabled <pyqtgraph.ViewBox.setMouseEnabled>`,
:func:`enableAutoRange <pyqtgraph.ViewBox.enableAutoRange>`,
:func:`disableAutoRange <pyqtgraph.ViewBox.disableAutoRange>`,
:func:`setAspectLocked <pyqtgraph.ViewBox.setAspectLocked>`,
:func:`register <pyqtgraph.ViewBox.register>`,
:func:`unregister <pyqtgraph.ViewBox.unregister>`
For all
other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`.
"""
def __init__(self, parent=None, **kargs):
GraphicsView.__init__(self, parent)
@ -29,7 +48,8 @@ class PlotWidget(GraphicsView):
self.plotItem = PlotItem(**kargs)
self.setCentralItem(self.plotItem)
## Explicitly wrap methods from plotItem
for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled']:
## NOTE: If you change this list, update the documentation above as well.
for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', 'register', 'unregister', 'viewRect']:
setattr(self, m, getattr(self.plotItem, m))
#QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged)
self.plotItem.sigRangeChanged.connect(self.viewRangeChanged)