From 025f2e55753e35462d31612600daba00b6994775 Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Sun, 10 Feb 2013 17:45:16 -0500 Subject: [PATCH] merged many new features from ACQ4 --- __init__.py | 1 + colormap.py | 109 +++++++++++++------------------- functions.py | 8 +-- graphicsItems/GraphItem.py | 16 +++-- parametertree/parameterTypes.py | 18 ++++-- widgets/ColorMapWidget.py | 40 +++++++++++- widgets/GradientWidget.py | 20 +++++- widgets/ScatterPlotWidget.py | 4 +- 8 files changed, 133 insertions(+), 83 deletions(-) diff --git a/__init__.py b/__init__.py index da197aa6..b72777ed 100644 --- a/__init__.py +++ b/__init__.py @@ -187,6 +187,7 @@ from .SRTTransform3D import SRTTransform3D from .functions import * from .graphicsWindows import * from .SignalProxy import * +from .colormap import * from .ptime import time diff --git a/colormap.py b/colormap.py index c7e683fb..d6169209 100644 --- a/colormap.py +++ b/colormap.py @@ -3,6 +3,25 @@ import scipy.interpolate from pyqtgraph.Qt import QtGui, QtCore class ColorMap(object): + """ + A ColorMap defines a relationship between a scalar value and a range of colors. + ColorMaps are commonly used for false-coloring monochromatic images, coloring + scatter-plot points, and coloring surface plots by height. + + Each color map is defined by a set of colors, each corresponding to a + particular scalar value. For example: + + | 0.0 -> black + | 0.2 -> red + | 0.6 -> yellow + | 1.0 -> white + + The colors for intermediate values are determined by interpolating between + the two nearest colors in either RGB or HSV color space. + + To provide user-defined color mappings, see :class:`GradientWidget `. + """ + ## color interpolation modes RGB = 1 @@ -54,7 +73,16 @@ class ColorMap(object): def map(self, data, mode='byte'): """ + Return an array of colors corresponding to the values in *data*. Data must be either a scalar position or an array (any shape) of positions. + + The *mode* argument determines the type of data returned: + + =========== =============================================================== + byte (default) Values are returned as 0-255 unsigned bytes. + float Values are returned as 0.0-1.0 floats. + qcolor Values are returned as an array of QColor objects. + =========== =============================================================== """ if isinstance(mode, basestring): mode = self.enumMap[mode.lower()] @@ -80,16 +108,19 @@ class ColorMap(object): return interp def mapToQColor(self, data): + """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.QCOLOR) def mapToByte(self, data): + """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.BYTE) def mapToFloat(self, data): + """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.FLOAT) def getGradient(self, p1=None, p2=None): - """Return a QLinearGradient object.""" + """Return a QLinearGradient object spanning from QPoints p1 to p2.""" if p1 == None: p1 = QtCore.QPointF(0,0) if p2 == None: @@ -119,7 +150,7 @@ class ColorMap(object): return g def getColors(self, mode=None): - """Return list of all colors converted to the specified mode. + """Return list of all color stops converted to the specified mode. If mode is None, then no conversion is done.""" if isinstance(mode, basestring): mode = self.enumMap[mode.lower()] @@ -158,75 +189,19 @@ class ColorMap(object): self.stopsCache[mode] = (self.pos, color) return self.stopsCache[mode] - #def getColor(self, x, toQColor=True): - #""" - #Return a color for a given value. - - #============= ================================================================== - #**Arguments** - #x Value (position on gradient) of requested color. - #toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. - #============= ================================================================== - #""" - #ticks = self.listTicks() - #if x <= ticks[0][1]: - #c = ticks[0][0].color - #if toQColor: - #return QtGui.QColor(c) # always copy colors before handing them out - #else: - #return (c.red(), c.green(), c.blue(), c.alpha()) - #if x >= ticks[-1][1]: - #c = ticks[-1][0].color - #if toQColor: - #return QtGui.QColor(c) # always copy colors before handing them out - #else: - #return (c.red(), c.green(), c.blue(), c.alpha()) - - #x2 = ticks[0][1] - #for i in range(1,len(ticks)): - #x1 = x2 - #x2 = ticks[i][1] - #if x1 <= x and x2 >= x: - #break - - #dx = (x2-x1) - #if dx == 0: - #f = 0. - #else: - #f = (x-x1) / dx - #c1 = ticks[i-1][0].color - #c2 = ticks[i][0].color - #if self.colorMode == 'rgb': - #r = c1.red() * (1.-f) + c2.red() * f - #g = c1.green() * (1.-f) + c2.green() * f - #b = c1.blue() * (1.-f) + c2.blue() * f - #a = c1.alpha() * (1.-f) + c2.alpha() * f - #if toQColor: - #return QtGui.QColor(int(r), int(g), int(b), int(a)) - #else: - #return (r,g,b,a) - #elif self.colorMode == 'hsv': - #h1,s1,v1,_ = c1.getHsv() - #h2,s2,v2,_ = c2.getHsv() - #h = h1 * (1.-f) + h2 * f - #s = s1 * (1.-f) + s2 * f - #v = v1 * (1.-f) + v2 * f - #c = QtGui.QColor() - #c.setHsv(h,s,v) - #if toQColor: - #return c - #else: - #return (c.red(), c.green(), c.blue(), c.alpha()) - def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'): """ Return an RGB(A) lookup table (ndarray). ============= ============================================================================ **Arguments** - nPts The number of points in the returned lookup table. - alpha True, False, or None - Specifies whether or not alpha values are included - in the table. If alpha is None, it will be automatically determined. + start The starting value in the lookup table (default=0.0) + stop The final value in the lookup table (default=1.0) + nPts The number of points in the returned lookup table. + alpha True, False, or None - Specifies whether or not alpha values are included + in the table. If alpha is None, it will be automatically determined. + mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'. + See :func:`map() `. ============= ============================================================================ """ if isinstance(mode, basestring): @@ -249,7 +224,9 @@ class ColorMap(object): return np.any(self.color[:,3] != max) def isMapTrivial(self): - """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" + """ + Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0. + """ if len(self.pos) != 2: return False if self.pos[0] != 0.0 or self.pos[1] != 1.0: diff --git a/functions.py b/functions.py index 467649e8..9e35b35b 100644 --- a/functions.py +++ b/functions.py @@ -566,8 +566,8 @@ def transformCoordinates(tr, coords, transpose=False): def solve3DTransform(points1, points2): """ - Find a 3D transformation matrix that maps points1 onto points2 - points must be specified as a list of 4 Vectors. + Find a 3D transformation matrix that maps points1 onto points2. + Points must be specified as a list of 4 Vectors. """ if not HAVE_SCIPY: raise Exception("This function depends on the scipy library, but it does not appear to be importable.") @@ -583,8 +583,8 @@ def solve3DTransform(points1, points2): def solveBilinearTransform(points1, points2): """ - Find a bilinear transformation matrix (2x4) that maps points1 onto points2 - points must be specified as a list of 4 Vector, Point, QPointF, etc. + Find a bilinear transformation matrix (2x4) that maps points1 onto points2. + Points must be specified as a list of 4 Vector, Point, QPointF, etc. To use this matrix to map a point [x,y]:: diff --git a/graphicsItems/GraphItem.py b/graphicsItems/GraphItem.py index 9a0f1244..bf03fef6 100644 --- a/graphicsItems/GraphItem.py +++ b/graphicsItems/GraphItem.py @@ -8,8 +8,9 @@ __all__ = ['GraphItem'] class GraphItem(GraphicsObject): - """A GraphItem displays graph information (as in 'graph theory', not 'graphics') as - a set of nodes connected by lines. + """A GraphItem displays graph information as + a set of nodes connected by lines (as in 'graph theory', not 'graphics'). + Useful for drawing networks, trees, etc. """ def __init__(self, **kwds): @@ -28,19 +29,24 @@ class GraphItem(GraphicsObject): ============ ========================================================= Arguments - pos (N,2) array of the positions of each node in the graph + pos (N,2) array of the positions of each node in the graph. adj (M,2) array of connection data. Each row contains indexes of two nodes that are connected. pen The pen to use when drawing lines between connected nodes. May be one of: + * QPen * a single argument to pass to pg.mkPen * a record array of length M - with fields (red, green, blue, alpha, width). + with fields (red, green, blue, alpha, width). Note + that using this option may have a significant performance + cost. * None (to disable connection drawing) * 'default' to use the default foreground color. + symbolPen The pen used for drawing nodes. - **opts All other keyword arguments are given to ScatterPlotItem + ``**opts`` All other keyword arguments are given to + :func:`ScatterPlotItem.setData() ` to affect the appearance of nodes (symbol, size, brush, etc.) ============ ========================================================= diff --git a/parametertree/parameterTypes.py b/parametertree/parameterTypes.py index 84db9f06..28e1e618 100644 --- a/parametertree/parameterTypes.py +++ b/parametertree/parameterTypes.py @@ -14,10 +14,20 @@ class WidgetParameterItem(ParameterItem): """ ParameterTree item with: - - label in second column for displaying value - - simple widget for editing value (displayed instead of label when item is selected) - - button that resets value to default - - provides SpinBox, CheckBox, LineEdit, and ColorButton types + * label in second column for displaying value + * simple widget for editing value (displayed instead of label when item is selected) + * button that resets value to default + + ================= ============================================================= + Registered Types: + int Displays a :class:`SpinBox ` in integer + mode. + float Displays a :class:`SpinBox `. + bool Displays a QCheckBox + str Displays a QLineEdit + color Displays a :class:`ColorButton ` + colormap Displays a :class:`GradientWidget ` + ================= ============================================================= This class can be subclassed by overriding makeWidget() to provide a custom widget. """ diff --git a/widgets/ColorMapWidget.py b/widgets/ColorMapWidget.py index 69a5e10a..619d639a 100644 --- a/widgets/ColorMapWidget.py +++ b/widgets/ColorMapWidget.py @@ -9,9 +9,14 @@ __all__ = ['ColorMapWidget'] class ColorMapWidget(ptree.ParameterTree): """ This class provides a widget allowing the user to customize color mapping - for multi-column data. - """ + for multi-column data. Given a list of field names, the user may specify + multiple criteria for assigning colors to each record in a numpy record array. + Multiple criteria are evaluated and combined into a single color for each + record by user-defined compositing methods. + For simpler color mapping using a single gradient editor, see + :class:`GradientWidget ` + """ sigColorMapChanged = QtCore.Signal(object) def __init__(self): @@ -51,6 +56,25 @@ class ColorMapParameter(ptree.types.GroupParameter): return self.fields.keys() def setFields(self, fields): + """ + Set the list of fields to be used by the mapper. + + The format of *fields* is:: + + [ (fieldName, {options}), ... ] + + ============== ============================================================ + Field Options: + mode Either 'range' or 'enum' (default is range). For 'range', + The user may specify a gradient of colors to be applied + linearly across a specific range of values. For 'enum', + the user specifies a single color for each unique value + (see *values* option). + units String indicating the units of the data for this field. + values List of unique values for which the user may assign a + color when mode=='enum'. + ============== ============================================================ + """ self.fields = OrderedDict(fields) #self.fields = fields #self.fields.sort() @@ -58,6 +82,18 @@ class ColorMapParameter(ptree.types.GroupParameter): self.setAddList(names) def map(self, data, mode='byte'): + """ + Return an array of colors corresponding to *data*. + + ========= ================================================================= + Arguments + data A numpy record array where the fields in data.dtype match those + defined by a prior call to setFields(). + mode Either 'byte' or 'float'. For 'byte', the method returns an array + of dtype ubyte with values scaled 0-255. For 'float', colors are + returned as 0.0-1.0 float values. + ========= ================================================================= + """ colors = np.zeros((len(data),4)) for item in self.children(): if not item['Enabled']: diff --git a/widgets/GradientWidget.py b/widgets/GradientWidget.py index 2b9b52d2..1723a94b 100644 --- a/widgets/GradientWidget.py +++ b/widgets/GradientWidget.py @@ -9,11 +9,27 @@ __all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider'] class GradientWidget(GraphicsView): - + """ + Widget displaying an editable color gradient. The user may add, move, recolor, + or remove colors from the gradient. Additionally, a context menu allows the + user to select from pre-defined gradients. + """ sigGradientChanged = QtCore.Signal(object) sigGradientChangeFinished = QtCore.Signal(object) def __init__(self, parent=None, orientation='bottom', *args, **kargs): + """ + The *orientation* argument may be 'bottom', 'top', 'left', or 'right' + indicating whether the gradient is displayed horizontally (top, bottom) + or vertically (left, right) and on what side of the gradient the editable + ticks will appear. + + All other arguments are passed to + :func:`GradientEditorItem.__init__ `. + + Note: For convenience, this class wraps methods from + :class:`GradientEditorItem `. + """ GraphicsView.__init__(self, parent, useOpenGL=False, background=None) self.maxDim = 31 kargs['tickPen'] = 'k' @@ -32,6 +48,8 @@ class GradientWidget(GraphicsView): #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) def setOrientation(self, ort): + """Set the orientation of the widget. May be one of 'bottom', 'top', + 'left', or 'right'.""" self.item.setOrientation(ort) self.orientation = ort self.setMaxDim() diff --git a/widgets/ScatterPlotWidget.py b/widgets/ScatterPlotWidget.py index 85f5489a..2e1c1918 100644 --- a/widgets/ScatterPlotWidget.py +++ b/widgets/ScatterPlotWidget.py @@ -57,7 +57,9 @@ class ScatterPlotWidget(QtGui.QSplitter): def setFields(self, fields): """ Set the list of field names/units to be processed. - Format is: [(name, units), ...] + + The format of *fields* is the same as used by + :func:`ColorMapWidget.setFields ` """ self.fields = OrderedDict(fields) self.fieldList.clear()