diff --git a/doc/source/apireference.rst b/doc/source/apireference.rst index 9742568a..c4dc64aa 100644 --- a/doc/source/apireference.rst +++ b/doc/source/apireference.rst @@ -6,6 +6,7 @@ Contents: .. toctree:: :maxdepth: 2 + config_options functions graphicsItems/index widgets/index diff --git a/doc/source/config_options.rst b/doc/source/config_options.rst new file mode 100644 index 00000000..6dd441ce --- /dev/null +++ b/doc/source/config_options.rst @@ -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 + diff --git a/examples/ImageView.py b/examples/ImageView.py index 881d8cdd..514858f0 100644 --- a/examples/ImageView.py +++ b/examples/ImageView.py @@ -17,6 +17,8 @@ import numpy as np from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg +pg.setConfigOptions(imageAxisOrder='normal') + app = QtGui.QApplication([]) ## 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 = 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 diff --git a/examples/ROItypes.py b/examples/ROItypes.py index 95b938cd..dd89255a 100644 --- a/examples/ROItypes.py +++ b/examples/ROItypes.py @@ -8,23 +8,15 @@ from pyqtgraph.Qt import QtCore, QtGui import numpy as np import pyqtgraph as pg +pg.setConfigOptions(imageAxisOrder='normal') + ## create GUI app = QtGui.QApplication([]) w = pg.GraphicsWindow(size=(800,800), border=True) - 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.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 @@ -37,6 +29,11 @@ arr[:, 75] = 5 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 im1 = pg.ImageItem(arr) im2 = pg.ImageItem(arr) @@ -44,6 +41,7 @@ v.addItem(im1) v.addItem(im2) im2.moveBy(110, 20) v.setRange(QtCore.QRectF(0, 0, 200, 120)) +im1.scale(0.8, 0.5) im3 = pg.ImageItem() v2 = w.addViewBox(1,0) diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py index d26f507e..3516472f 100644 --- a/examples/VideoSpeedTest.py +++ b/examples/VideoSpeedTest.py @@ -103,6 +103,9 @@ def mkData(): if dtype[0] != 'float': data = np.clip(data, 0, mx) 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) data = cache[dtype] diff --git a/examples/imageAnalysis.py b/examples/imageAnalysis.py index 8283144e..be64815e 100644 --- a/examples/imageAnalysis.py +++ b/examples/imageAnalysis.py @@ -12,6 +12,7 @@ import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import numpy as np +pg.setConfigOptions(imageAxisOrder='normal') pg.mkQApp() win = pg.GraphicsLayoutWidget() diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index 9aafa5b5..e472854c 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -59,6 +59,10 @@ CONFIG_OPTIONS = { '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) '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 def setConfigOptions(**opts): + """Set global configuration options. + + Each keyword argument sets one global option. + """ CONFIG_OPTIONS.update(opts) def getConfigOption(opt): + """Return the value of a single global configuration option. + """ return CONFIG_OPTIONS[opt] diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py index 13cc9dce..a79fcb15 100644 --- a/pyqtgraph/graphicsItems/ImageItem.py +++ b/pyqtgraph/graphicsItems/ImageItem.py @@ -7,6 +7,8 @@ from .. import functions as fn from .. import debug as debug from .GraphicsObject import GraphicsObject from ..Point import Point +from .. import getConfigOption + __all__ = ['ImageItem'] @@ -28,7 +30,6 @@ class ImageItem(GraphicsObject): for controlling the levels and lookup table used to display the image. """ - sigImageChanged = QtCore.Signal() sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu @@ -86,12 +87,14 @@ class ImageItem(GraphicsObject): def width(self): if self.image is None: return None - return self.image.shape[0] + axis = 0 if getConfigOption('imageAxisOrder') == 'legacy' else 1 + return self.image.shape[axis] def height(self): if self.image is None: return None - return self.image.shape[1] + axis = 1 if getConfigOption('imageAxisOrder') == 'legacy' else 0 + return self.image.shape[axis] def boundingRect(self): 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 3D (width, height, RGBa). The array dtype must be integer or floating 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 levels based on the maximum and minimum values in the data. 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 in the image. If the image array has dtype uint8, no rescaling is necessary. opacity (float 0.0-1.0) - compositionMode see :func:`setCompositionMode ` + compositionMode See :func:`setCompositionMode ` 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 screen resolution. This improves performance for large images and 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 `. + + """ profile = debug.Profiler() @@ -330,8 +347,14 @@ class ImageItem(GraphicsObject): self._effectiveLut = efflut lut = self._effectiveLut 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) def paint(self, p, *args): @@ -347,7 +370,8 @@ class ImageItem(GraphicsObject): p.setCompositionMode(self.paintMode) 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') if self.border is not None: p.setPen(self.border) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 51853c61..d5f41af4 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -21,6 +21,7 @@ from math import cos, sin from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem +from .. import getConfigOption __all__ = [ 'ROI', @@ -1074,7 +1075,11 @@ class ROI(GraphicsObject): Used to determine the relationship between the ROI and the boundaries of *data*. 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 ` is set to + 'normal', then the axes are instead specified in + (y, x) order. returnMappedCoords (bool) If True, the array slice is returned along with a corresponding array of coordinates that were used to extract data from the original array. @@ -1143,6 +1148,12 @@ class ROI(GraphicsObject): origin = (origin.x(), origin.y()) 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 diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 27e64c4c..68f1b54b 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -30,6 +30,7 @@ from ..graphicsItems.GradientEditorItem import addGradientListToDocstring from .. import ptime as ptime from .. import debug as debug from ..SignalProxy import SignalProxy +from .. import getConfigOption try: from bottleneck import nanmin, nanmax @@ -203,9 +204,10 @@ class ImageView(QtGui.QWidget): """ Set the image to be displayed in the widget. - ================== ======================================================================= + ================== =========================================================================== **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 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. @@ -222,7 +224,19 @@ class ImageView(QtGui.QWidget): and *scale*. autoHistogramRange If True, the histogram y-range is automatically scaled to fit the 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 `. + """ profiler = debug.Profiler() @@ -252,15 +266,17 @@ class ImageView(QtGui.QWidget): profiler() if axes is None: + xy = (0, 1) if getConfigOption('imageAxisOrder') == 'legacy' else (1, 0) + 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: 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: - 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: - 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: raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) elif isinstance(axes, dict): @@ -542,6 +558,7 @@ class ImageView(QtGui.QWidget): axes = (1, 2) else: return + data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True) if data is not None: while data.ndim > 1: diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index 06015e44..73f25d44 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -63,7 +63,7 @@ class GraphicsView(QtGui.QGraphicsView): :func:`mkColor `. By default, the background color is determined using the 'backgroundColor' configuration option (see - :func:`setConfigOption `. + :func:`setConfigOptions `). ============== ============================================================ """