documentation updates
This commit is contained in:
parent
44f2a0ecc4
commit
4eadccdcc1
@ -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)
|
||||
|
@ -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):
|
||||
|
16
__init__.py
16
__init__.py
@ -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)
|
||||
|
@ -9,3 +9,4 @@ Contents:
|
||||
functions
|
||||
graphicsItems/index
|
||||
widgets/index
|
||||
graphicsscene/index
|
8
documentation/source/graphicsscene/graphicsscene.rst
Normal file
8
documentation/source/graphicsscene/graphicsscene.rst
Normal file
@ -0,0 +1,8 @@
|
||||
GraphicsScene
|
||||
=============
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphicsScene
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.GraphicsScene.__init__
|
||||
|
5
documentation/source/graphicsscene/hoverevent.rst
Normal file
5
documentation/source/graphicsscene/hoverevent.rst
Normal file
@ -0,0 +1,5 @@
|
||||
HoverEvent
|
||||
==========
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.HoverEvent
|
||||
:members:
|
12
documentation/source/graphicsscene/index.rst
Normal file
12
documentation/source/graphicsscene/index.rst
Normal file
@ -0,0 +1,12 @@
|
||||
GraphicsScene and Mouse Events
|
||||
==============================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
graphicsscene
|
||||
hoverevent
|
||||
mouseclickevent
|
||||
mousedragevent
|
5
documentation/source/graphicsscene/mouseclickevent.rst
Normal file
5
documentation/source/graphicsscene/mouseclickevent.rst
Normal file
@ -0,0 +1,5 @@
|
||||
MouseClickEvent
|
||||
===============
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseClickEvent
|
||||
:members:
|
5
documentation/source/graphicsscene/mousedragevent.rst
Normal file
5
documentation/source/graphicsscene/mousedragevent.rst
Normal file
@ -0,0 +1,5 @@
|
||||
MouseDragEvent
|
||||
==============
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseDragEvent
|
||||
:members:
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 = {}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user