diff --git a/flowchart/library/Filters.py b/flowchart/library/Filters.py index 1819b01e..a88ea40e 100644 --- a/flowchart/library/Filters.py +++ b/flowchart/library/Filters.py @@ -187,7 +187,7 @@ class HistogramDetrend(CtrlNode): """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" nodeName = 'HistogramDetrend' uiTemplate = [ - ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000}), + ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) ] diff --git a/functions.py b/functions.py index ab60e63e..624e90b4 100644 --- a/functions.py +++ b/functions.py @@ -409,12 +409,12 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs): -def makeARGB(data, lut=None, levels=None): +def makeARGB(data, lut=None, levels=None, useRGBA=False): """ Convert a 2D or 3D array into an ARGB array suitable for building QImages Will optionally do scaling and/or table lookups to determine final colors. - Returns the ARGB array and a boolean indicating whether there is alpha channel data. + Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data. Arguments: data - 2D or 3D numpy array of int/float types @@ -433,6 +433,8 @@ def makeARGB(data, lut=None, levels=None): Lookup tables can be built using GradientWidget. levels - List [min, max]; optionally rescale data before converting through the lookup table. rescaled = (data-min) * len(lut) / (max-min) + useRGBA - If True, the data is returned in RGBA order. The default is + False, which returns in BGRA order for use with QImage. """ @@ -580,8 +582,11 @@ def makeARGB(data, lut=None, levels=None): prof.mark('4') - - order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + if useRGBA: + order = [0,1,2,3] ## array comes out RGBA + else: + order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + if data.shape[2] == 1: for i in xrange(3): imgData[..., order[i]] = data[..., 0] @@ -732,7 +737,85 @@ def rescaleData(data, scale, offset): #return facets +def isocurve(data, level): + """ + Generate isocurve from 2D data using marching squares algorithm. + + *data* 2D numpy array of scalar values + *level* The level at which to generate an isosurface + + This function is SLOW; plenty of room for optimization here. + """ + sideTable = [ + [], + [0,1], + [1,2], + [0,2], + [0,3], + [1,3], + [0,1,2,3], + [2,3], + [2,3], + [0,1,2,3], + [1,3], + [0,3], + [0,2], + [1,2], + [0,1], + [] + ] + + edgeKey=[ + [(0,1),(0,0)], + [(0,0), (1,0)], + [(1,0), (1,1)], + [(1,1), (0,1)] + ] + + + lines = [] + + ## mark everything below the isosurface level + mask = data < level + + ### make four sub-fields and compute indexes for grid cells + index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) + fields = np.empty((2,2), dtype=object) + slices = [slice(0,-1), slice(1,None)] + for i in [0,1]: + for j in [0,1]: + fields[i,j] = mask[slices[i], slices[j]] + #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme + vertIndex = i+2*j + #print i,j,k," : ", fields[i,j,k], 2**vertIndex + index += fields[i,j] * 2**vertIndex + #print index + #print index + + ## add lines + for i in xrange(index.shape[0]): # data x-axis + for j in xrange(index.shape[1]): # data y-axis + sides = sideTable[index[i,j]] + for l in range(0, len(sides), 2): ## faces for this grid cell + edges = sides[l:l+2] + pts = [] + for m in [0,1]: # points in this face + p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge + p2 = edgeKey[edges[m]][1] + v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 + v2 = data[i+p2[0], j+p2[1]] + f = (level-v1) / (v2-v1) + fi = 1.0 - f + p = ( ## interpolate between corners + p1[0]*fi + p2[0]*f + i + 0.5, + p1[1]*fi + p2[1]*f + j + 0.5 + ) + pts.append(p) + lines.append(pts) + + return lines ## a list of pairs of points + def isosurface(data, level): """ diff --git a/graphicsItems/IsocurveItem.py b/graphicsItems/IsocurveItem.py new file mode 100644 index 00000000..62e582fc --- /dev/null +++ b/graphicsItems/IsocurveItem.py @@ -0,0 +1,38 @@ + + +from GraphicsObject import * +import pyqtgraph.functions as fn +from pyqtgraph.Qt import QtGui + + +class IsocurveItem(GraphicsObject): + """ + Item displaying an isocurve of a 2D array. + + To align this item correctly with an ImageItem, + call isocurve.setParentItem(image) + """ + + def __init__(self, data, level, pen='w'): + GraphicsObject.__init__(self) + + lines = fn.isocurve(data, level) + + self.path = QtGui.QPainterPath() + self.setPen(pen) + + for line in lines: + self.path.moveTo(*line[0]) + self.path.lineTo(*line[1]) + + def setPen(self, *args, **kwargs): + self.pen = fn.mkPen(*args, **kwargs) + self.update() + + def boundingRect(self): + return self.path.boundingRect() + + def paint(self, p, *args): + p.setPen(self.pen) + p.drawPath(self.path) + \ No newline at end of file diff --git a/widgets/GradientWidget.py b/widgets/GradientWidget.py index 47d6ab45..bcc50b69 100644 --- a/widgets/GradientWidget.py +++ b/widgets/GradientWidget.py @@ -55,6 +55,7 @@ class GradientWidget(GraphicsView): self.setMaximumHeight(16777215) def __getattr__(self, attr): + ### wrap methods from GradientEditorItem return getattr(self.item, attr)