Added imageAxisOrder config option

Added global config documentation
ROIs don't exactly work yet..
This commit is contained in:
Luke Campagnola 2016-04-27 22:33:51 -07:00
parent e46be6ddec
commit bee5878915
11 changed files with 135 additions and 28 deletions

View File

@ -6,6 +6,7 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
config_options
functions functions
graphicsItems/index graphicsItems/index
widgets/index widgets/index

View File

@ -0,0 +1,40 @@
.. currentmodule:: pyqtgraph
.. _apiref_config:
Global Configuration Options
============================
PyQtGraph has several global configuration options that allow you to change its
default behavior. These can be accessed using the :func:`setConfigOptions` and
:func:`getConfigOption` functions:
================== =================== ================== ================================================================================
**Option** **Type** **Default**
leftButtonPan bool True If True, dragging the left mouse button over a ViewBox
causes the view to be panned. If False, then dragging
the left mouse button draws a rectangle that the
ViewBox will zoom to.
foreground See :func:`mkColor` 'd' Default foreground color for text, lines, axes, etc.
background See :func:`mkColor` 'k' Default background for :class:`GraphicsView`.
antialias bool False Enabling antialiasing causes lines to be drawn with
smooth edges at the cost of reduced performance.
imageAxisOrder str 'legacy' For 'normal', image data is expected in the standard row-major (row, col) order.
For 'legacy', image data is expected in reversed column-major (col, row) order.
The default is 'legacy' for backward compatibility, but this may
change in the future.
editorCommand str or None None Command used to invoke code editor from ConsoleWidget.
exitCleanup bool True Attempt to work around some exit crash bugs in PyQt and PySide.
useWeave bool False Use weave to speed up some operations, if it is available.
weaveDebug bool False Print full error message if weave compile fails.
useOpenGL bool False Enable OpenGL in GraphicsView. This can have unpredictable effects on stability
and performance.
enableExperimental bool False Enable experimental features (the curious can search for this key in the code).
crashWarning bool False If True, print warnings about situations that may result in a crash.
================== =================== ================== ================================================================================
.. autofunction:: pyqtgraph.setConfigOptions
.. autofunction:: pyqtgraph.getConfigOption

View File

@ -17,6 +17,8 @@ import numpy as np
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg import pyqtgraph as pg
pg.setConfigOptions(imageAxisOrder='normal')
app = QtGui.QApplication([]) app = QtGui.QApplication([])
## Create window with ImageView widget ## Create window with ImageView widget
@ -42,7 +44,7 @@ sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30)) sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,np.newaxis,np.newaxis] * 3 sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,50:60] += sig data[:,50:60,30:40] += sig
## Display the data and assign each frame a time value from 1.0 to 3.0 ## Display the data and assign each frame a time value from 1.0 to 3.0

View File

@ -8,23 +8,15 @@ from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
pg.setConfigOptions(imageAxisOrder='normal')
## create GUI ## create GUI
app = QtGui.QApplication([]) app = QtGui.QApplication([])
w = pg.GraphicsWindow(size=(800,800), border=True) w = pg.GraphicsWindow(size=(800,800), border=True)
v = w.addViewBox(colspan=2) v = w.addViewBox(colspan=2)
#w = QtGui.QMainWindow()
#w.resize(800,800)
#v = pg.GraphicsView()
v.invertY(True) ## Images usually have their Y-axis pointing downward v.invertY(True) ## Images usually have their Y-axis pointing downward
v.setAspectLocked(True) v.setAspectLocked(True)
#v.enableMouse(True)
#v.autoPixelScale = False
#w.setCentralWidget(v)
#s = v.scene()
#v.setRange(QtCore.QRectF(-2, -2, 220, 220))
## Create image to display ## Create image to display
@ -37,6 +29,11 @@ arr[:, 75] = 5
arr[50, :] = 10 arr[50, :] = 10
arr[:, 50] = 10 arr[:, 50] = 10
# add an arrow for asymmetry
arr[10, :50] = 10
arr[9:12, 44:48] = 10
arr[8:13, 44:46] = 10
## Create image items, add to scene and set position ## Create image items, add to scene and set position
im1 = pg.ImageItem(arr) im1 = pg.ImageItem(arr)
im2 = pg.ImageItem(arr) im2 = pg.ImageItem(arr)
@ -44,6 +41,7 @@ v.addItem(im1)
v.addItem(im2) v.addItem(im2)
im2.moveBy(110, 20) im2.moveBy(110, 20)
v.setRange(QtCore.QRectF(0, 0, 200, 120)) v.setRange(QtCore.QRectF(0, 0, 200, 120))
im1.scale(0.8, 0.5)
im3 = pg.ImageItem() im3 = pg.ImageItem()
v2 = w.addViewBox(1,0) v2 = w.addViewBox(1,0)

View File

@ -103,6 +103,9 @@ def mkData():
if dtype[0] != 'float': if dtype[0] != 'float':
data = np.clip(data, 0, mx) data = np.clip(data, 0, mx)
data = data.astype(dt) data = data.astype(dt)
data[:, 10, 10:50] = mx
data[:, 9:12, 48] = mx
data[:, 8:13, 47] = mx
cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration) cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration)
data = cache[dtype] data = cache[dtype]

View File

@ -12,6 +12,7 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
pg.setConfigOptions(imageAxisOrder='normal')
pg.mkQApp() pg.mkQApp()
win = pg.GraphicsLayoutWidget() win = pg.GraphicsLayoutWidget()

View File

@ -59,6 +59,10 @@ CONFIG_OPTIONS = {
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
'crashWarning': False, # If True, print warnings about situations that may result in a crash 'crashWarning': False, # If True, print warnings about situations that may result in a crash
'imageAxisOrder': 'legacy', # For 'normal', image data is expected in the standard (row, col) order.
# For 'legacy', image data is expected in reversed (col, row) order.
# The default is 'legacy' for backward compatibility, but this will
# change in the future.
} }
@ -66,9 +70,15 @@ def setConfigOption(opt, value):
CONFIG_OPTIONS[opt] = value CONFIG_OPTIONS[opt] = value
def setConfigOptions(**opts): def setConfigOptions(**opts):
"""Set global configuration options.
Each keyword argument sets one global option.
"""
CONFIG_OPTIONS.update(opts) CONFIG_OPTIONS.update(opts)
def getConfigOption(opt): def getConfigOption(opt):
"""Return the value of a single global configuration option.
"""
return CONFIG_OPTIONS[opt] return CONFIG_OPTIONS[opt]

View File

@ -7,6 +7,8 @@ from .. import functions as fn
from .. import debug as debug from .. import debug as debug
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
from ..Point import Point from ..Point import Point
from .. import getConfigOption
__all__ = ['ImageItem'] __all__ = ['ImageItem']
@ -28,7 +30,6 @@ class ImageItem(GraphicsObject):
for controlling the levels and lookup table used to display the image. for controlling the levels and lookup table used to display the image.
""" """
sigImageChanged = QtCore.Signal() sigImageChanged = QtCore.Signal()
sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu
@ -86,12 +87,14 @@ class ImageItem(GraphicsObject):
def width(self): def width(self):
if self.image is None: if self.image is None:
return None return None
return self.image.shape[0] axis = 0 if getConfigOption('imageAxisOrder') == 'legacy' else 1
return self.image.shape[axis]
def height(self): def height(self):
if self.image is None: if self.image is None:
return None return None
return self.image.shape[1] axis = 1 if getConfigOption('imageAxisOrder') == 'legacy' else 0
return self.image.shape[axis]
def boundingRect(self): def boundingRect(self):
if self.image is None: if self.image is None:
@ -190,7 +193,7 @@ class ImageItem(GraphicsObject):
image (numpy array) Specifies the image data. May be 2D (width, height) or image (numpy array) Specifies the image data. May be 2D (width, height) or
3D (width, height, RGBa). The array dtype must be integer or floating 3D (width, height, RGBa). The array dtype must be integer or floating
point of any bit depth. For 3D arrays, the third dimension must point of any bit depth. For 3D arrays, the third dimension must
be of length 3 (RGB) or 4 (RGBA). be of length 3 (RGB) or 4 (RGBA). See *notes* below.
autoLevels (bool) If True, this forces the image to automatically select autoLevels (bool) If True, this forces the image to automatically select
levels based on the maximum and minimum values in the data. levels based on the maximum and minimum values in the data.
By default, this argument is true unless the levels argument is By default, this argument is true unless the levels argument is
@ -201,12 +204,26 @@ class ImageItem(GraphicsObject):
data. By default, this will be set to the minimum and maximum values data. By default, this will be set to the minimum and maximum values
in the image. If the image array has dtype uint8, no rescaling is necessary. in the image. If the image array has dtype uint8, no rescaling is necessary.
opacity (float 0.0-1.0) opacity (float 0.0-1.0)
compositionMode see :func:`setCompositionMode <pyqtgraph.ImageItem.setCompositionMode>` compositionMode See :func:`setCompositionMode <pyqtgraph.ImageItem.setCompositionMode>`
border Sets the pen used when drawing the image border. Default is None. border Sets the pen used when drawing the image border. Default is None.
autoDownsample (bool) If True, the image is automatically downsampled to match the autoDownsample (bool) If True, the image is automatically downsampled to match the
screen resolution. This improves performance for large images and screen resolution. This improves performance for large images and
reduces aliasing. reduces aliasing.
================= ========================================================================= ================= =========================================================================
**Notes:**
For backward compatibility, image data is assumed to be in column-major order (column, row).
However, most image data is stored in row-major order (row, column) and will need to be
transposed before calling setImage()::
imageitem.setImage(imagedata.T)
This requirement can be changed by the ``imageAxisOrder``
:ref:`global configuration option <apiref_config>`.
""" """
profile = debug.Profiler() profile = debug.Profiler()
@ -331,7 +348,13 @@ class ImageItem(GraphicsObject):
lut = self._effectiveLut lut = self._effectiveLut
levels = None levels = None
argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=levels) # Assume images are in column-major order for backward compatibility
# (most images are in row-major order)
if getConfigOption('imageAxisOrder') == 'legacy':
image = image.transpose((1, 0, 2)[:image.ndim])
argb, alpha = fn.makeARGB(image, lut=lut, levels=levels)
self.qimage = fn.makeQImage(argb, alpha, transpose=False) self.qimage = fn.makeQImage(argb, alpha, transpose=False)
def paint(self, p, *args): def paint(self, p, *args):
@ -347,7 +370,8 @@ class ImageItem(GraphicsObject):
p.setCompositionMode(self.paintMode) p.setCompositionMode(self.paintMode)
profile('set comp mode') profile('set comp mode')
p.drawImage(QtCore.QRectF(0,0,self.image.shape[0],self.image.shape[1]), self.qimage) shape = self.image.shape[:2] if getConfigOption('imageAxisOrder') == 'legacy' else self.image.shape[:2][::-1]
p.drawImage(QtCore.QRectF(0,0,*shape), self.qimage)
profile('p.drawImage') profile('p.drawImage')
if self.border is not None: if self.border is not None:
p.setPen(self.border) p.setPen(self.border)

View File

@ -21,6 +21,7 @@ from math import cos, sin
from .. import functions as fn from .. import functions as fn
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
from .UIGraphicsItem import UIGraphicsItem from .UIGraphicsItem import UIGraphicsItem
from .. import getConfigOption
__all__ = [ __all__ = [
'ROI', 'ROI',
@ -1074,7 +1075,11 @@ class ROI(GraphicsObject):
Used to determine the relationship between the Used to determine the relationship between the
ROI and the boundaries of *data*. ROI and the boundaries of *data*.
axes (length-2 tuple) Specifies the axes in *data* that axes (length-2 tuple) Specifies the axes in *data* that
correspond to the x and y axes of *img*. correspond to the (x, y) axes of *img*. If the
global configuration variable
:ref:`imageAxisOrder <apiref_config>` is set to
'normal', then the axes are instead specified in
(y, x) order.
returnMappedCoords (bool) If True, the array slice is returned along returnMappedCoords (bool) If True, the array slice is returned along
with a corresponding array of coordinates that were with a corresponding array of coordinates that were
used to extract data from the original array. used to extract data from the original array.
@ -1143,6 +1148,12 @@ class ROI(GraphicsObject):
origin = (origin.x(), origin.y()) origin = (origin.x(), origin.y())
shape = [abs(shape[0]/sx), abs(shape[1]/sy)] shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
origin = (origin.x(), origin.y())
if getConfigOption('imageAxisOrder') == 'normal':
vectors = [vectors[1][::-1], vectors[0][::-1]]
shape = shape[::-1]
origin = origin[::-1]
return shape, vectors, origin return shape, vectors, origin

View File

@ -30,6 +30,7 @@ from ..graphicsItems.GradientEditorItem import addGradientListToDocstring
from .. import ptime as ptime from .. import ptime as ptime
from .. import debug as debug from .. import debug as debug
from ..SignalProxy import SignalProxy from ..SignalProxy import SignalProxy
from .. import getConfigOption
try: try:
from bottleneck import nanmin, nanmax from bottleneck import nanmin, nanmax
@ -203,9 +204,10 @@ class ImageView(QtGui.QWidget):
""" """
Set the image to be displayed in the widget. Set the image to be displayed in the widget.
================== ======================================================================= ================== ===========================================================================
**Arguments:** **Arguments:**
img (numpy array) the image to be displayed. img (numpy array) the image to be displayed. See :func:`ImageItem.setImage` and
*notes* below.
xvals (numpy array) 1D array of z-axis values corresponding to the third axis 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. 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. autoRange (bool) whether to scale/pan the view to fit the image.
@ -222,7 +224,19 @@ class ImageView(QtGui.QWidget):
and *scale*. and *scale*.
autoHistogramRange If True, the histogram y-range is automatically scaled to fit the autoHistogramRange If True, the histogram y-range is automatically scaled to fit the
image data. image data.
================== ======================================================================= ================== ===========================================================================
**Notes:**
For backward compatibility, image data is assumed to be in column-major order (column, row).
However, most image data is stored in row-major order (row, column) and will need to be
transposed before calling setImage()::
imageview.setImage(imagedata.T)
This requirement can be changed by the ``imageAxisOrder``
:ref:`global configuration option <apiref_config>`.
""" """
profiler = debug.Profiler() profiler = debug.Profiler()
@ -252,15 +266,17 @@ class ImageView(QtGui.QWidget):
profiler() profiler()
if axes is None: if axes is None:
xy = (0, 1) if getConfigOption('imageAxisOrder') == 'legacy' else (1, 0)
if img.ndim == 2: if img.ndim == 2:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None} self.axes = {'t': None, 'x': xy[0], 'y': xy[1], 'c': None}
elif img.ndim == 3: elif img.ndim == 3:
if img.shape[2] <= 4: if img.shape[2] <= 4:
self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2} self.axes = {'t': None, 'x': xy[0], 'y': xy[1], 'c': 2}
else: else:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None} self.axes = {'t': 0, 'x': xy[0]+1, 'y': xy[1]+1, 'c': None}
elif img.ndim == 4: elif img.ndim == 4:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3} self.axes = {'t': 0, 'x': xy[0]+1, 'y': xy[1]+1, 'c': 3}
else: else:
raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) raise Exception("Can not interpret image with dimensions %s" % (str(img.shape)))
elif isinstance(axes, dict): elif isinstance(axes, dict):
@ -542,6 +558,7 @@ class ImageView(QtGui.QWidget):
axes = (1, 2) axes = (1, 2)
else: else:
return return
data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True) data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True)
if data is not None: if data is not None:
while data.ndim > 1: while data.ndim > 1:

View File

@ -63,7 +63,7 @@ class GraphicsView(QtGui.QGraphicsView):
:func:`mkColor <pyqtgraph.mkColor>`. By :func:`mkColor <pyqtgraph.mkColor>`. By
default, the background color is determined using the default, the background color is determined using the
'backgroundColor' configuration option (see 'backgroundColor' configuration option (see
:func:`setConfigOption <pyqtgraph.setConfigOption>`. :func:`setConfigOptions <pyqtgraph.setConfigOptions>`).
============== ============================================================ ============== ============================================================
""" """