diff --git a/canvas/Canvas.py b/canvas/Canvas.py
index bcc17a27..17a39c2b 100644
--- a/canvas/Canvas.py
+++ b/canvas/Canvas.py
@@ -92,6 +92,15 @@ class Canvas(QtGui.QWidget):
if name is not None:
self.registeredName = CanvasManager.instance().registerCanvas(self, name)
self.ui.redirectCombo.setHostName(self.registeredName)
+
+ self.menu = QtGui.QMenu()
+ #self.menu.setTitle("Image")
+ remAct = QtGui.QAction("Remove item", self.menu)
+ remAct.triggered.connect(self.removeClicked)
+ self.menu.addAction(remAct)
+ self.menu.remAct = remAct
+ self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent
+
def storeSvg(self):
self.ui.view.writeSvg()
@@ -513,10 +522,20 @@ class Canvas(QtGui.QWidget):
listItem.setCheckState(0, QtCore.Qt.Unchecked)
def removeItem(self, item):
+ if isinstance(item, QtGui.QTreeWidgetItem):
+ item = item.canvasItem()
+
+
if isinstance(item, CanvasItem):
item.setCanvas(None)
- self.itemList.removeTopLevelItem(item.listItem)
+ listItem = item.listItem
+ listItem.canvasItem = None
+ item.listItem = None
+ self.itemList.removeTopLevelItem(listItem)
self.items.remove(item)
+ ctrl = item.ctrlWidget()
+ ctrl.hide()
+ self.ui.ctrlLayout.removeWidget(ctrl)
else:
if hasattr(item, '_canvasItem'):
self.removeItem(item._canvasItem)
@@ -555,7 +574,15 @@ class Canvas(QtGui.QWidget):
#self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item)
self.sigItemTransformChangeFinished.emit(self, item)
-
+ def itemListContextMenuEvent(self, ev):
+ self.menuItem = self.itemList.itemAt(ev.pos())
+ self.menu.popup(ev.globalPos())
+
+ def removeClicked(self):
+ self.removeItem(self.menuItem)
+ self.menuItem = None
+ import gc
+ gc.collect()
class SelectBox(ROI):
def __init__(self, scalable=False):
diff --git a/console/Console.py b/console/Console.py
index 4d5b691a..6fbe44a7 100644
--- a/console/Console.py
+++ b/console/Console.py
@@ -208,8 +208,11 @@ class ConsoleWidget(QtGui.QWidget):
#self.stdout.write("
")
self.output.insertPlainText(strn)
#self.stdout.write(strn)
-
+
def displayException(self):
+ """
+ Display the current exception and stack.
+ """
tb = traceback.format_exc()
lines = []
indent = 4
diff --git a/documentation/source/functions.rst b/documentation/source/functions.rst
index ad43ca06..65f2c202 100644
--- a/documentation/source/functions.rst
+++ b/documentation/source/functions.rst
@@ -8,7 +8,7 @@ Simple Data Display Functions
.. autofunction:: pyqtgraph.image
-
+.. autofunction:: pyqtgraph.dbg
Color, Pen, and Brush Functions
-------------------------------
@@ -34,6 +34,8 @@ Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and
.. autofunction:: pyqtgraph.colorStr
+.. autofunction:: pyqtgraph.glColor
+
Data Slicing
------------
@@ -41,6 +43,18 @@ Data Slicing
.. autofunction:: pyqtgraph.affineSlice
+Coordinate Transformation
+-------------------------
+
+.. autofunction:: pyqtgraph.transformToArray
+
+.. autofunction:: pyqtgraph.transformCoordinates
+
+.. autofunction:: pyqtgraph.solve3DTransform
+
+.. autofunction:: pyqtgraph.solveBilinearTransform
+
+
SI Unit Conversion Functions
----------------------------
@@ -59,6 +73,12 @@ Image Preparation Functions
.. autofunction:: pyqtgraph.makeQImage
+.. autofunction:: pyqtgraph.applyLookupTable
+
+.. autofunction:: pyqtgraph.rescaleData
+
+.. autofunction:: pyqtgraph.imageToArray
+
Mesh Generation Functions
-------------------------
@@ -68,4 +88,13 @@ Mesh Generation Functions
.. autofunction:: pyqtgraph.isosurface
+Miscellaneous Functions
+-----------------------
+
+.. autofunction:: pyqtgraph.pseudoScatter
+
+.. autofunction:: pyqtgraph.systemInfo
+
+
+
diff --git a/examples/GLIsosurface.py b/examples/GLIsosurface.py
index 32478605..4324d9cf 100644
--- a/examples/GLIsosurface.py
+++ b/examples/GLIsosurface.py
@@ -45,9 +45,9 @@ data = np.abs(np.fromfunction(psi, (50,50,100)))
print("Generating isosurface..")
-verts = pg.isosurface(data, data.max()/4.)
+verts, faces = pg.isosurface(data, data.max()/4.)
-md = gl.MeshData(vertexes=verts)
+md = gl.MeshData(vertexes=verts, faces=faces)
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[:,3] = 0.2
diff --git a/examples/__main__.py b/examples/__main__.py
index 1d69731c..6f4bf138 100644
--- a/examples/__main__.py
+++ b/examples/__main__.py
@@ -175,8 +175,11 @@ def testFile(name, f, exe, lib):
try:
%s
import %s
+ import sys
print("test complete")
+ sys.stdout.flush()
import pyqtgraph as pg
+ import time
while True: ## run a little event loop
pg.QtGui.QApplication.processEvents()
time.sleep(0.01)
@@ -186,7 +189,7 @@ except:
""" % ("import %s" % lib if lib != '' else "", os.path.splitext(os.path.split(fn)[1])[0])
#print code
- process = subprocess.Popen(['%s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ process = subprocess.Popen(['exec %s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8'))
#process.stdin.close()
output = ''
@@ -202,10 +205,11 @@ except:
fail = True
break
time.sleep(1)
- process.terminate()
+ process.kill()
+ #process.wait()
res = process.communicate()
- #if 'exception' in res[1].lower() or 'error' in res[1].lower():
- if fail:
+
+ if fail or 'exception' in res[1].decode().lower() or 'error' in res[1].decode().lower():
print('.' * (50-len(name)) + 'FAILED')
print(res[0].decode())
print(res[1].decode())
diff --git a/examples/isocurve.py b/examples/isocurve.py
index eea525b9..14a3e56a 100644
--- a/examples/isocurve.py
+++ b/examples/isocurve.py
@@ -19,6 +19,7 @@ frames = 200
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
data = np.concatenate([data, data], axis=0)
data = ndi.gaussian_filter(data, (10, 10, 10))[frames/2:frames + frames/2]
+data[:, 15:16, 15:17] += 1
win = pg.GraphicsWindow()
vb = win.addViewBox()
diff --git a/exporters/Exporter.py b/exporters/Exporter.py
index c3917fca..2540b327 100644
--- a/exporters/Exporter.py
+++ b/exporters/Exporter.py
@@ -73,7 +73,8 @@ class Exporter(object):
def getSourceRect(self):
if isinstance(self.item, pg.GraphicsScene):
- return self.item.getViewWidget().viewRect()
+ w = self.item.getViewWidget()
+ return w.viewportTransform().inverted()[0].mapRect(w.rect())
else:
return self.item.sceneBoundingRect()
diff --git a/exporters/SVGExporter.py b/exporters/SVGExporter.py
index 0538aae7..2d040282 100644
--- a/exporters/SVGExporter.py
+++ b/exporters/SVGExporter.py
@@ -36,11 +36,14 @@ class SVGExporter(Exporter):
return
self.svg = QtSvg.QSvgGenerator()
self.svg.setFileName(fileName)
- self.svg.setSize(QtCore.QSize(100,100))
- #self.svg.setResolution(600)
+ dpi = QtGui.QDesktopWidget().physicalDpiX()
+ ## not really sure why this works, but it seems to be important:
+ self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.))
+ self.svg.setResolution(dpi)
#self.svg.setViewBox()
targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
sourceRect = self.getSourceRect()
+
painter = QtGui.QPainter(self.svg)
try:
self.setExportMode(True)
diff --git a/functions.py b/functions.py
index b83a2186..3f5a01d9 100644
--- a/functions.py
+++ b/functions.py
@@ -491,9 +491,6 @@ def transformToArray(tr):
## map coordinates through transform
mapped = np.dot(m, coords)
"""
- if isinstance(tr, np.ndarray):
- return tr
-
#return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]])
## The order of elements given by the method names m11..m33 is misleading--
## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in
@@ -506,18 +503,28 @@ def transformToArray(tr):
else:
raise Exception("Transform argument must be either QTransform or QMatrix4x4.")
-def transformCoordinates(tr, coords):
+def transformCoordinates(tr, coords, transpose=False):
"""
Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4.
The shape of coords must be (2,...) or (3,...)
The mapping will _ignore_ any perspective transformations.
+
+ For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication.
+ Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To
+ allow this, use transpose=True.
+
"""
+
+ if transpose:
+ ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates.
+ coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1)))
+
nd = coords.shape[0]
- if not isinstance(tr, np.ndarray):
+ if isinstance(tr, np.ndarray):
+ m = tr
+ else:
m = transformToArray(tr)
m = m[:m.shape[0]-1] # remove perspective
- else:
- m = tr
## If coords are 3D and tr is 2D, assume no change for Z axis
if m.shape == (2,3) and nd == 3:
@@ -545,9 +552,15 @@ def transformCoordinates(tr, coords):
## map coordinates and return
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
mapped += translate
+
+ if transpose:
+ ## move first axis to end.
+ mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,))
return mapped
+
+
def solve3DTransform(points1, points2):
"""
Find a 3D transformation matrix that maps points1 onto points2
@@ -782,7 +795,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
if levels.shape != (data.shape[-1], 2):
raise Exception('levels must have shape (data.shape[-1], 2)')
else:
- print(levels)
+ print levels
raise Exception("levels argument must be 1D or 2D.")
#levels = np.array(levels)
#if levels.shape == (2,):
@@ -1066,16 +1079,43 @@ def imageToArray(img, copy=False, transpose=True):
#return facets
-def isocurve(data, level):
+def isocurve(data, level, connected=False, extendToEdge=False, path=False):
"""
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
+ ============= =========================================================
+ Arguments
+ data 2D numpy array of scalar values
+ level The level at which to generate an isosurface
+ connected If False, return a single long list of point pairs
+ If True, return multiple long lists of connected point
+ locations. (This is slower but better for drawing
+ continuous lines)
+ extendToEdge If True, extend the curves to reach the exact edges of
+ the data.
+ path if True, return a QPainterPath rather than a list of
+ vertex coordinates. This forces connected=True.
+ ============= =========================================================
This function is SLOW; plenty of room for optimization here.
"""
+ if path is True:
+ connected = True
+
+ if extendToEdge:
+ d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype)
+ d2[1:-1, 1:-1] = data
+ d2[0, 1:-1] = data[0]
+ d2[-1, 1:-1] = data[-1]
+ d2[1:-1, 0] = data[:, 0]
+ d2[1:-1, -1] = data[:, -1]
+ d2[0,0] = d2[0,1]
+ d2[0,-1] = d2[1,-1]
+ d2[-1,0] = d2[-1,1]
+ d2[-1,-1] = d2[-1,-2]
+ data = d2
+
sideTable = [
[],
[0,1],
@@ -1096,7 +1136,7 @@ def isocurve(data, level):
]
edgeKey=[
- [(0,1),(0,0)],
+ [(0,1), (0,0)],
[(0,0), (1,0)],
[(1,0), (1,1)],
[(1,1), (0,1)]
@@ -1140,347 +1180,482 @@ def isocurve(data, level):
p1[0]*fi + p2[0]*f + i + 0.5,
p1[1]*fi + p2[1]*f + j + 0.5
)
- pts.append(p)
+ if extendToEdge:
+ ## check bounds
+ p = (
+ min(data.shape[0]-2, max(0, p[0]-1)),
+ min(data.shape[1]-2, max(0, p[1]-1)),
+ )
+ if connected:
+ gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2
+ pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments)
+ else:
+ pts.append(p)
+
lines.append(pts)
- return lines ## a list of pairs of points
+ if not connected:
+ return lines
+
+ ## turn disjoint list of segments into continuous lines
+
+ #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]]
+ #lines = [[(float(a), a), (float(b), b)] for a,b in lines]
+ points = {} ## maps each point to its connections
+ for a,b in lines:
+ if a[1] not in points:
+ points[a[1]] = []
+ points[a[1]].append([a,b])
+ if b[1] not in points:
+ points[b[1]] = []
+ points[b[1]].append([b,a])
+
+ ## rearrange into chains
+ for k in points.keys():
+ try:
+ chains = points[k]
+ except KeyError: ## already used this point elsewhere
+ continue
+ #print "===========", k
+ for chain in chains:
+ #print " chain:", chain
+ x = None
+ while True:
+ if x == chain[-1][1]:
+ break ## nothing left to do on this chain
+
+ x = chain[-1][1]
+ if x == k:
+ break ## chain has looped; we're done and can ignore the opposite chain
+ y = chain[-2][1]
+ connects = points[x]
+ for conn in connects[:]:
+ if conn[1][1] != y:
+ #print " ext:", conn
+ chain.extend(conn[1:])
+ #print " del:", x
+ del points[x]
+ if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction
+ chains.pop()
+ break
+
+
+ ## extract point locations
+ lines = []
+ for chain in points.values():
+ if len(chain) == 2:
+ chain = chain[1][1:][::-1] + chain[0] # join together ends of chain
+ else:
+ chain = chain[0]
+ lines.append([p[0] for p in chain])
+
+ if not path:
+ return lines ## a list of pairs of points
+
+ path = QtGui.QPainterPath()
+ for line in lines:
+ path.moveTo(*line[0])
+ for p in line[1:]:
+ path.lineTo(*p)
+
+ return path
+def traceImage(image, values, smooth=0.5):
+ """
+ Convert an image to a set of QPainterPath curves.
+ One curve will be generated for each item in *values*; each curve outlines the area
+ of the image that is closer to its value than to any others.
+
+ If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
+ The parameter *smooth* is expressed in pixels.
+ """
+ import scipy.ndimage as ndi
+ if values.ndim == 2:
+ values = values.T
+ values = values[np.newaxis, np.newaxis, ...].astype(float)
+ image = image[..., np.newaxis].astype(float)
+ diff = np.abs(image-values)
+ if values.ndim == 4:
+ diff = diff.sum(axis=2)
+
+ labels = np.argmin(diff, axis=2)
+
+ paths = []
+ for i in range(diff.shape[-1]):
+ d = (labels==i).astype(float)
+ d = ndi.gaussian_filter(d, (smooth, smooth))
+ lines = isocurve(d, 0.5, connected=True, extendToEdge=True)
+ path = QtGui.QPainterPath()
+ for line in lines:
+ path.moveTo(*line[0])
+ for p in line[1:]:
+ path.lineTo(*p)
+
+ paths.append(path)
+ return paths
+
+
+
+IsosurfaceDataCache = None
def isosurface(data, level):
"""
Generate isosurface from volumetric data using marching cubes algorithm.
See Paul Bourke, "Polygonising a Scalar Field"
- (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/)
+ (http://paulbourke.net/geometry/polygonise/)
*data* 3D numpy array of scalar values
*level* The level at which to generate an isosurface
- Returns an array of vertex coordinates (N, 3, 3);
-
- This function is SLOW; plenty of room for optimization here.
+ Returns an array of vertex coordinates (Nv, 3) and an array of
+ per-face vertex indexes (Nf, 3)
"""
+ ## For improvement, see:
+ ##
+ ## Efficient implementation of Marching Cubes' cases with topological guarantees.
+ ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares.
+ ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003)
+
+ ## Precompute lookup tables on the first run
+ global IsosurfaceDataCache
+ if IsosurfaceDataCache is None:
+ ## map from grid cell index to edge index.
+ ## grid cell index tells us which corners are below the isosurface,
+ ## edge index tells us which edges are cut by the isosurface.
+ ## (Data stolen from Bourk; see above.)
+ edgeTable = np.array([
+ 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
+ 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+ 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
+ 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+ 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
+ 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+ 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
+ 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+ 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
+ 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+ 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
+ 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+ 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
+ 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+ 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
+ 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+ 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
+ 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+ 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
+ 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+ 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
+ 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+ 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
+ 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
+ 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
+ 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
+ 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
+ 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
+ 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
+ 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
+ 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
+ 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16)
+
+ ## Table of triangles to use for filling each grid cell.
+ ## Each set of three integers tells us which three edges to
+ ## draw a triangle between.
+ ## (Data stolen from Bourk; see above.)
+ triTable = [
+ [],
+ [0, 8, 3],
+ [0, 1, 9],
+ [1, 8, 3, 9, 8, 1],
+ [1, 2, 10],
+ [0, 8, 3, 1, 2, 10],
+ [9, 2, 10, 0, 2, 9],
+ [2, 8, 3, 2, 10, 8, 10, 9, 8],
+ [3, 11, 2],
+ [0, 11, 2, 8, 11, 0],
+ [1, 9, 0, 2, 3, 11],
+ [1, 11, 2, 1, 9, 11, 9, 8, 11],
+ [3, 10, 1, 11, 10, 3],
+ [0, 10, 1, 0, 8, 10, 8, 11, 10],
+ [3, 9, 0, 3, 11, 9, 11, 10, 9],
+ [9, 8, 10, 10, 8, 11],
+ [4, 7, 8],
+ [4, 3, 0, 7, 3, 4],
+ [0, 1, 9, 8, 4, 7],
+ [4, 1, 9, 4, 7, 1, 7, 3, 1],
+ [1, 2, 10, 8, 4, 7],
+ [3, 4, 7, 3, 0, 4, 1, 2, 10],
+ [9, 2, 10, 9, 0, 2, 8, 4, 7],
+ [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
+ [8, 4, 7, 3, 11, 2],
+ [11, 4, 7, 11, 2, 4, 2, 0, 4],
+ [9, 0, 1, 8, 4, 7, 2, 3, 11],
+ [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
+ [3, 10, 1, 3, 11, 10, 7, 8, 4],
+ [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
+ [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
+ [4, 7, 11, 4, 11, 9, 9, 11, 10],
+ [9, 5, 4],
+ [9, 5, 4, 0, 8, 3],
+ [0, 5, 4, 1, 5, 0],
+ [8, 5, 4, 8, 3, 5, 3, 1, 5],
+ [1, 2, 10, 9, 5, 4],
+ [3, 0, 8, 1, 2, 10, 4, 9, 5],
+ [5, 2, 10, 5, 4, 2, 4, 0, 2],
+ [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
+ [9, 5, 4, 2, 3, 11],
+ [0, 11, 2, 0, 8, 11, 4, 9, 5],
+ [0, 5, 4, 0, 1, 5, 2, 3, 11],
+ [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
+ [10, 3, 11, 10, 1, 3, 9, 5, 4],
+ [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
+ [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
+ [5, 4, 8, 5, 8, 10, 10, 8, 11],
+ [9, 7, 8, 5, 7, 9],
+ [9, 3, 0, 9, 5, 3, 5, 7, 3],
+ [0, 7, 8, 0, 1, 7, 1, 5, 7],
+ [1, 5, 3, 3, 5, 7],
+ [9, 7, 8, 9, 5, 7, 10, 1, 2],
+ [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
+ [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
+ [2, 10, 5, 2, 5, 3, 3, 5, 7],
+ [7, 9, 5, 7, 8, 9, 3, 11, 2],
+ [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
+ [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
+ [11, 2, 1, 11, 1, 7, 7, 1, 5],
+ [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
+ [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
+ [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
+ [11, 10, 5, 7, 11, 5],
+ [10, 6, 5],
+ [0, 8, 3, 5, 10, 6],
+ [9, 0, 1, 5, 10, 6],
+ [1, 8, 3, 1, 9, 8, 5, 10, 6],
+ [1, 6, 5, 2, 6, 1],
+ [1, 6, 5, 1, 2, 6, 3, 0, 8],
+ [9, 6, 5, 9, 0, 6, 0, 2, 6],
+ [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
+ [2, 3, 11, 10, 6, 5],
+ [11, 0, 8, 11, 2, 0, 10, 6, 5],
+ [0, 1, 9, 2, 3, 11, 5, 10, 6],
+ [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
+ [6, 3, 11, 6, 5, 3, 5, 1, 3],
+ [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
+ [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
+ [6, 5, 9, 6, 9, 11, 11, 9, 8],
+ [5, 10, 6, 4, 7, 8],
+ [4, 3, 0, 4, 7, 3, 6, 5, 10],
+ [1, 9, 0, 5, 10, 6, 8, 4, 7],
+ [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
+ [6, 1, 2, 6, 5, 1, 4, 7, 8],
+ [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
+ [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
+ [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
+ [3, 11, 2, 7, 8, 4, 10, 6, 5],
+ [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
+ [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
+ [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
+ [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
+ [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
+ [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
+ [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
+ [10, 4, 9, 6, 4, 10],
+ [4, 10, 6, 4, 9, 10, 0, 8, 3],
+ [10, 0, 1, 10, 6, 0, 6, 4, 0],
+ [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
+ [1, 4, 9, 1, 2, 4, 2, 6, 4],
+ [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
+ [0, 2, 4, 4, 2, 6],
+ [8, 3, 2, 8, 2, 4, 4, 2, 6],
+ [10, 4, 9, 10, 6, 4, 11, 2, 3],
+ [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
+ [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
+ [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
+ [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
+ [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
+ [3, 11, 6, 3, 6, 0, 0, 6, 4],
+ [6, 4, 8, 11, 6, 8],
+ [7, 10, 6, 7, 8, 10, 8, 9, 10],
+ [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
+ [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
+ [10, 6, 7, 10, 7, 1, 1, 7, 3],
+ [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
+ [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
+ [7, 8, 0, 7, 0, 6, 6, 0, 2],
+ [7, 3, 2, 6, 7, 2],
+ [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
+ [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
+ [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
+ [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
+ [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
+ [0, 9, 1, 11, 6, 7],
+ [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
+ [7, 11, 6],
+ [7, 6, 11],
+ [3, 0, 8, 11, 7, 6],
+ [0, 1, 9, 11, 7, 6],
+ [8, 1, 9, 8, 3, 1, 11, 7, 6],
+ [10, 1, 2, 6, 11, 7],
+ [1, 2, 10, 3, 0, 8, 6, 11, 7],
+ [2, 9, 0, 2, 10, 9, 6, 11, 7],
+ [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
+ [7, 2, 3, 6, 2, 7],
+ [7, 0, 8, 7, 6, 0, 6, 2, 0],
+ [2, 7, 6, 2, 3, 7, 0, 1, 9],
+ [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
+ [10, 7, 6, 10, 1, 7, 1, 3, 7],
+ [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
+ [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
+ [7, 6, 10, 7, 10, 8, 8, 10, 9],
+ [6, 8, 4, 11, 8, 6],
+ [3, 6, 11, 3, 0, 6, 0, 4, 6],
+ [8, 6, 11, 8, 4, 6, 9, 0, 1],
+ [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
+ [6, 8, 4, 6, 11, 8, 2, 10, 1],
+ [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
+ [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
+ [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
+ [8, 2, 3, 8, 4, 2, 4, 6, 2],
+ [0, 4, 2, 4, 6, 2],
+ [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
+ [1, 9, 4, 1, 4, 2, 2, 4, 6],
+ [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
+ [10, 1, 0, 10, 0, 6, 6, 0, 4],
+ [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
+ [10, 9, 4, 6, 10, 4],
+ [4, 9, 5, 7, 6, 11],
+ [0, 8, 3, 4, 9, 5, 11, 7, 6],
+ [5, 0, 1, 5, 4, 0, 7, 6, 11],
+ [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
+ [9, 5, 4, 10, 1, 2, 7, 6, 11],
+ [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
+ [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
+ [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
+ [7, 2, 3, 7, 6, 2, 5, 4, 9],
+ [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
+ [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
+ [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
+ [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
+ [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
+ [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
+ [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
+ [6, 9, 5, 6, 11, 9, 11, 8, 9],
+ [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
+ [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
+ [6, 11, 3, 6, 3, 5, 5, 3, 1],
+ [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
+ [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
+ [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
+ [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
+ [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
+ [9, 5, 6, 9, 6, 0, 0, 6, 2],
+ [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
+ [1, 5, 6, 2, 1, 6],
+ [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
+ [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
+ [0, 3, 8, 5, 6, 10],
+ [10, 5, 6],
+ [11, 5, 10, 7, 5, 11],
+ [11, 5, 10, 11, 7, 5, 8, 3, 0],
+ [5, 11, 7, 5, 10, 11, 1, 9, 0],
+ [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
+ [11, 1, 2, 11, 7, 1, 7, 5, 1],
+ [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
+ [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
+ [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
+ [2, 5, 10, 2, 3, 5, 3, 7, 5],
+ [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
+ [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
+ [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
+ [1, 3, 5, 3, 7, 5],
+ [0, 8, 7, 0, 7, 1, 1, 7, 5],
+ [9, 0, 3, 9, 3, 5, 5, 3, 7],
+ [9, 8, 7, 5, 9, 7],
+ [5, 8, 4, 5, 10, 8, 10, 11, 8],
+ [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
+ [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
+ [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
+ [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
+ [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
+ [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
+ [9, 4, 5, 2, 11, 3],
+ [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
+ [5, 10, 2, 5, 2, 4, 4, 2, 0],
+ [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
+ [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
+ [8, 4, 5, 8, 5, 3, 3, 5, 1],
+ [0, 4, 5, 1, 0, 5],
+ [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
+ [9, 4, 5],
+ [4, 11, 7, 4, 9, 11, 9, 10, 11],
+ [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
+ [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
+ [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
+ [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
+ [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
+ [11, 7, 4, 11, 4, 2, 2, 4, 0],
+ [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
+ [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
+ [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
+ [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
+ [1, 10, 2, 8, 7, 4],
+ [4, 9, 1, 4, 1, 7, 7, 1, 3],
+ [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
+ [4, 0, 3, 7, 4, 3],
+ [4, 8, 7],
+ [9, 10, 8, 10, 11, 8],
+ [3, 0, 9, 3, 9, 11, 11, 9, 10],
+ [0, 1, 10, 0, 10, 8, 8, 10, 11],
+ [3, 1, 10, 11, 3, 10],
+ [1, 2, 11, 1, 11, 9, 9, 11, 8],
+ [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
+ [0, 2, 11, 8, 0, 11],
+ [3, 2, 11],
+ [2, 3, 8, 2, 8, 10, 10, 8, 9],
+ [9, 10, 2, 0, 9, 2],
+ [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
+ [1, 10, 2],
+ [1, 3, 8, 9, 1, 8],
+ [0, 9, 1],
+ [0, 3, 8],
+ []
+ ]
+ edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2)
+ [0, 0, 0, 0],
+ [1, 0, 0, 1],
+ [0, 1, 0, 0],
+ [0, 0, 0, 1],
+ [0, 0, 1, 0],
+ [1, 0, 1, 1],
+ [0, 1, 1, 0],
+ [0, 0, 1, 1],
+ [0, 0, 0, 2],
+ [1, 0, 0, 2],
+ [1, 1, 0, 2],
+ [0, 1, 0, 2],
+ #[9, 9, 9, 9] ## fake
+ ], dtype=np.ubyte)
+ nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte)
+ faceShiftTables = [None]
+ for i in range(1,6):
+ ## compute lookup table of index: vertexes mapping
+ faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte)
+ faceTableInds = np.argwhere(nTableFaces == i)
+ faceTableI[faceTableInds[:,0]] = np.array([triTable[j] for j in faceTableInds])
+ faceTableI = faceTableI.reshape((len(triTable), i, 3))
+ faceShiftTables.append(edgeShifts[faceTableI])
+
+ ## Let's try something different:
+ #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup)
+ #for i,f in enumerate(triTable):
+ #f = np.array(f + [12] * (15-len(f))).reshape(5,3)
+ #faceTable[i] = edgeShifts[f]
+
+
+ IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces)
+ else:
+ faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache
- ## map from grid cell index to edge index.
- ## grid cell index tells us which corners are below the isosurface,
- ## edge index tells us which edges are cut by the isosurface.
- ## (Data stolen from Bourk; see above.)
- edgeTable = [
- 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
- 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
- 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
- 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
- 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
- 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
- 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
- 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
- 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
- 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
- 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
- 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
- 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
- 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
- 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
- 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
- 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
- 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
- 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
- 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
- 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
- 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
- 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
- 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
- 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
- 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
- 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
- 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
- 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
- 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
- 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
- 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ]
- ## Table of triangles to use for filling each grid cell.
- ## Each set of three integers tells us which three edges to
- ## draw a triangle between.
- ## (Data stolen from Bourk; see above.)
- triTable = [
- [],
- [0, 8, 3],
- [0, 1, 9],
- [1, 8, 3, 9, 8, 1],
- [1, 2, 10],
- [0, 8, 3, 1, 2, 10],
- [9, 2, 10, 0, 2, 9],
- [2, 8, 3, 2, 10, 8, 10, 9, 8],
- [3, 11, 2],
- [0, 11, 2, 8, 11, 0],
- [1, 9, 0, 2, 3, 11],
- [1, 11, 2, 1, 9, 11, 9, 8, 11],
- [3, 10, 1, 11, 10, 3],
- [0, 10, 1, 0, 8, 10, 8, 11, 10],
- [3, 9, 0, 3, 11, 9, 11, 10, 9],
- [9, 8, 10, 10, 8, 11],
- [4, 7, 8],
- [4, 3, 0, 7, 3, 4],
- [0, 1, 9, 8, 4, 7],
- [4, 1, 9, 4, 7, 1, 7, 3, 1],
- [1, 2, 10, 8, 4, 7],
- [3, 4, 7, 3, 0, 4, 1, 2, 10],
- [9, 2, 10, 9, 0, 2, 8, 4, 7],
- [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
- [8, 4, 7, 3, 11, 2],
- [11, 4, 7, 11, 2, 4, 2, 0, 4],
- [9, 0, 1, 8, 4, 7, 2, 3, 11],
- [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
- [3, 10, 1, 3, 11, 10, 7, 8, 4],
- [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
- [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
- [4, 7, 11, 4, 11, 9, 9, 11, 10],
- [9, 5, 4],
- [9, 5, 4, 0, 8, 3],
- [0, 5, 4, 1, 5, 0],
- [8, 5, 4, 8, 3, 5, 3, 1, 5],
- [1, 2, 10, 9, 5, 4],
- [3, 0, 8, 1, 2, 10, 4, 9, 5],
- [5, 2, 10, 5, 4, 2, 4, 0, 2],
- [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
- [9, 5, 4, 2, 3, 11],
- [0, 11, 2, 0, 8, 11, 4, 9, 5],
- [0, 5, 4, 0, 1, 5, 2, 3, 11],
- [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
- [10, 3, 11, 10, 1, 3, 9, 5, 4],
- [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
- [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
- [5, 4, 8, 5, 8, 10, 10, 8, 11],
- [9, 7, 8, 5, 7, 9],
- [9, 3, 0, 9, 5, 3, 5, 7, 3],
- [0, 7, 8, 0, 1, 7, 1, 5, 7],
- [1, 5, 3, 3, 5, 7],
- [9, 7, 8, 9, 5, 7, 10, 1, 2],
- [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
- [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
- [2, 10, 5, 2, 5, 3, 3, 5, 7],
- [7, 9, 5, 7, 8, 9, 3, 11, 2],
- [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
- [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
- [11, 2, 1, 11, 1, 7, 7, 1, 5],
- [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
- [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
- [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
- [11, 10, 5, 7, 11, 5],
- [10, 6, 5],
- [0, 8, 3, 5, 10, 6],
- [9, 0, 1, 5, 10, 6],
- [1, 8, 3, 1, 9, 8, 5, 10, 6],
- [1, 6, 5, 2, 6, 1],
- [1, 6, 5, 1, 2, 6, 3, 0, 8],
- [9, 6, 5, 9, 0, 6, 0, 2, 6],
- [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
- [2, 3, 11, 10, 6, 5],
- [11, 0, 8, 11, 2, 0, 10, 6, 5],
- [0, 1, 9, 2, 3, 11, 5, 10, 6],
- [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
- [6, 3, 11, 6, 5, 3, 5, 1, 3],
- [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
- [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
- [6, 5, 9, 6, 9, 11, 11, 9, 8],
- [5, 10, 6, 4, 7, 8],
- [4, 3, 0, 4, 7, 3, 6, 5, 10],
- [1, 9, 0, 5, 10, 6, 8, 4, 7],
- [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
- [6, 1, 2, 6, 5, 1, 4, 7, 8],
- [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
- [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
- [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
- [3, 11, 2, 7, 8, 4, 10, 6, 5],
- [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
- [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
- [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
- [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
- [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
- [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
- [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
- [10, 4, 9, 6, 4, 10],
- [4, 10, 6, 4, 9, 10, 0, 8, 3],
- [10, 0, 1, 10, 6, 0, 6, 4, 0],
- [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
- [1, 4, 9, 1, 2, 4, 2, 6, 4],
- [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
- [0, 2, 4, 4, 2, 6],
- [8, 3, 2, 8, 2, 4, 4, 2, 6],
- [10, 4, 9, 10, 6, 4, 11, 2, 3],
- [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
- [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
- [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
- [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
- [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
- [3, 11, 6, 3, 6, 0, 0, 6, 4],
- [6, 4, 8, 11, 6, 8],
- [7, 10, 6, 7, 8, 10, 8, 9, 10],
- [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
- [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
- [10, 6, 7, 10, 7, 1, 1, 7, 3],
- [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
- [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
- [7, 8, 0, 7, 0, 6, 6, 0, 2],
- [7, 3, 2, 6, 7, 2],
- [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
- [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
- [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
- [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
- [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
- [0, 9, 1, 11, 6, 7],
- [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
- [7, 11, 6],
- [7, 6, 11],
- [3, 0, 8, 11, 7, 6],
- [0, 1, 9, 11, 7, 6],
- [8, 1, 9, 8, 3, 1, 11, 7, 6],
- [10, 1, 2, 6, 11, 7],
- [1, 2, 10, 3, 0, 8, 6, 11, 7],
- [2, 9, 0, 2, 10, 9, 6, 11, 7],
- [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
- [7, 2, 3, 6, 2, 7],
- [7, 0, 8, 7, 6, 0, 6, 2, 0],
- [2, 7, 6, 2, 3, 7, 0, 1, 9],
- [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
- [10, 7, 6, 10, 1, 7, 1, 3, 7],
- [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
- [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
- [7, 6, 10, 7, 10, 8, 8, 10, 9],
- [6, 8, 4, 11, 8, 6],
- [3, 6, 11, 3, 0, 6, 0, 4, 6],
- [8, 6, 11, 8, 4, 6, 9, 0, 1],
- [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
- [6, 8, 4, 6, 11, 8, 2, 10, 1],
- [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
- [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
- [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
- [8, 2, 3, 8, 4, 2, 4, 6, 2],
- [0, 4, 2, 4, 6, 2],
- [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
- [1, 9, 4, 1, 4, 2, 2, 4, 6],
- [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
- [10, 1, 0, 10, 0, 6, 6, 0, 4],
- [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
- [10, 9, 4, 6, 10, 4],
- [4, 9, 5, 7, 6, 11],
- [0, 8, 3, 4, 9, 5, 11, 7, 6],
- [5, 0, 1, 5, 4, 0, 7, 6, 11],
- [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
- [9, 5, 4, 10, 1, 2, 7, 6, 11],
- [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
- [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
- [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
- [7, 2, 3, 7, 6, 2, 5, 4, 9],
- [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
- [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
- [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
- [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
- [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
- [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
- [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
- [6, 9, 5, 6, 11, 9, 11, 8, 9],
- [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
- [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
- [6, 11, 3, 6, 3, 5, 5, 3, 1],
- [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
- [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
- [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
- [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
- [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
- [9, 5, 6, 9, 6, 0, 0, 6, 2],
- [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
- [1, 5, 6, 2, 1, 6],
- [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
- [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
- [0, 3, 8, 5, 6, 10],
- [10, 5, 6],
- [11, 5, 10, 7, 5, 11],
- [11, 5, 10, 11, 7, 5, 8, 3, 0],
- [5, 11, 7, 5, 10, 11, 1, 9, 0],
- [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
- [11, 1, 2, 11, 7, 1, 7, 5, 1],
- [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
- [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
- [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
- [2, 5, 10, 2, 3, 5, 3, 7, 5],
- [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
- [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
- [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
- [1, 3, 5, 3, 7, 5],
- [0, 8, 7, 0, 7, 1, 1, 7, 5],
- [9, 0, 3, 9, 3, 5, 5, 3, 7],
- [9, 8, 7, 5, 9, 7],
- [5, 8, 4, 5, 10, 8, 10, 11, 8],
- [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
- [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
- [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
- [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
- [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
- [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
- [9, 4, 5, 2, 11, 3],
- [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
- [5, 10, 2, 5, 2, 4, 4, 2, 0],
- [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
- [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
- [8, 4, 5, 8, 5, 3, 3, 5, 1],
- [0, 4, 5, 1, 0, 5],
- [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
- [9, 4, 5],
- [4, 11, 7, 4, 9, 11, 9, 10, 11],
- [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
- [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
- [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
- [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
- [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
- [11, 7, 4, 11, 4, 2, 2, 4, 0],
- [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
- [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
- [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
- [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
- [1, 10, 2, 8, 7, 4],
- [4, 9, 1, 4, 1, 7, 7, 1, 3],
- [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
- [4, 0, 3, 7, 4, 3],
- [4, 8, 7],
- [9, 10, 8, 10, 11, 8],
- [3, 0, 9, 3, 9, 11, 11, 9, 10],
- [0, 1, 10, 0, 10, 8, 8, 10, 11],
- [3, 1, 10, 11, 3, 10],
- [1, 2, 11, 1, 11, 9, 9, 11, 8],
- [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
- [0, 2, 11, 8, 0, 11],
- [3, 2, 11],
- [2, 3, 8, 2, 8, 10, 10, 8, 9],
- [9, 10, 2, 0, 9, 2],
- [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
- [1, 10, 2],
- [1, 3, 8, 9, 1, 8],
- [0, 9, 1],
- [0, 3, 8],
- []
- ]
-
- ## translation between edge index and
- ## the vertex indexes that bound the edge
- edgeKey = [
- [(0,0,0), (1,0,0)],
- [(1,0,0), (1,1,0)],
- [(1,1,0), (0,1,0)],
- [(0,1,0), (0,0,0)],
- [(0,0,1), (1,0,1)],
- [(1,0,1), (1,1,1)],
- [(1,1,1), (0,1,1)],
- [(0,1,1), (0,0,1)],
- [(0,0,0), (0,0,1)],
- [(1,0,0), (1,0,1)],
- [(1,1,0), (1,1,1)],
- [(0,1,0), (0,1,1)],
- ]
-
-
-
- facets = []
## mark everything below the isosurface level
mask = data < level
@@ -1494,35 +1669,93 @@ def isosurface(data, level):
for k in [0,1]:
fields[i,j,k] = mask[slices[i], slices[j], slices[k]]
vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme
- #print i,j,k," : ", fields[i,j,k], 2**vertIndex
index += fields[i,j,k] * 2**vertIndex
- #print index
- #print index
- ## add facets
- for i in range(index.shape[0]): # data x-axis
- for j in range(index.shape[1]): # data y-axis
- for k in range(index.shape[2]): # data z-axis
- tris = triTable[index[i,j,k]]
- for l in range(0, len(tris), 3): ## faces for this grid cell
- edges = tris[l:l+3]
- pts = []
- for m in [0,1,2]: # points in this face
- p1 = edgeKey[edges[m]][0]
- p2 = edgeKey[edges[m]][1]
- v1 = data[i+p1[0], j+p1[1], k+p1[2]]
- v2 = data[i+p2[0], j+p2[1], k+p2[2]]
- 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,
- p1[2]*fi + p2[2]*f + k + 0.5
- )
- pts.append(p)
- facets.append(pts)
+ ### Generate table of edges that have been cut
+ cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32)
+ edges = edgeTable[index]
+ for i, shift in enumerate(edgeShifts[:12]):
+ slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)]
+ cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i
+
+ ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions
+ m = cutEdges > 0
+ vertexInds = np.argwhere(m) ## argwhere is slow!
+ vertexes = vertexInds[:,:3].astype(np.float32)
+ dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2])
+
+ ## re-use the cutEdges array as a lookup table for vertex IDs
+ cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0])
+
+ for i in [0,1,2]:
+ vim = vertexInds[:,3] == i
+ vi = vertexInds[vim, :3]
+ viFlat = (vi * (np.array(data.strides[:3]) / data.itemsize)[np.newaxis,:]).sum(axis=1)
+ v1 = dataFlat[viFlat]
+ v2 = dataFlat[viFlat + data.strides[i]/data.itemsize]
+ vertexes[vim,i] += (level-v1) / (v2-v1)
+
+ ### compute the set of vertex indexes for each face.
+
+ ## This works, but runs a bit slower.
+ #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face
+ #cellInds = index[cells[:,0], cells[:,1], cells[:,2]]
+ #verts = faceTable[cellInds]
+ #mask = verts[...,0,0] != 9
+ #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges
+ #verts = verts[mask]
+ #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want.
+
+
+ ## To allow this to be vectorized efficiently, we count the number of faces in each
+ ## grid cell and handle each group of cells with the same number together.
+ ## determine how many faces to assign to each grid cell
+ nFaces = nTableFaces[index]
+ totFaces = nFaces.sum()
+ faces = np.empty((totFaces, 3), dtype=np.uint32)
+ ptr = 0
+ #import debug
+ #p = debug.Profiler('isosurface', disabled=False)
+
+ ## this helps speed up an indexing operation later on
+ cs = np.array(cutEdges.strides)/cutEdges.itemsize
+ cutEdges = cutEdges.flatten()
- return np.array(facets)
+ ## this, strangely, does not seem to help.
+ #ins = np.array(index.strides)/index.itemsize
+ #index = index.flatten()
+
+ for i in range(1,6):
+ ### expensive:
+ #p.mark('1')
+ cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive)
+ #p.mark('2')
+ if cells.shape[0] == 0:
+ continue
+ #cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)]
+ cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round
+ #p.mark('3')
+
+ ### expensive:
+ verts = faceShiftTables[i][cellInds]
+ #p.mark('4')
+ verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges
+ verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:])
+ #p.mark('5')
+
+ ### expensive:
+ #print verts.shape
+ verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2)
+ #vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want.
+ vertInds = cutEdges[verts]
+ #p.mark('6')
+ nv = vertInds.shape[0]
+ #p.mark('7')
+ faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3))
+ #p.mark('8')
+ ptr += nv
+
+ return vertexes, faces
@@ -1592,4 +1825,4 @@ def pseudoScatter(data, spacing=None, shuffle=True):
yvals[i] = y
- return yvals[np.argsort(inds)] ## un-shuffle values before returning
+ return yvals[np.argsort(inds)] ## un-shuffle values before returning
\ No newline at end of file
diff --git a/graphicsItems/GraphicsItem.py b/graphicsItems/GraphicsItem.py
index 4f4753b1..43b8148c 100644
--- a/graphicsItems/GraphicsItem.py
+++ b/graphicsItems/GraphicsItem.py
@@ -3,6 +3,7 @@ from pyqtgraph.GraphicsScene import GraphicsScene
from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import weakref
+import operator
class GraphicsItem(object):
"""
@@ -395,8 +396,16 @@ class GraphicsItem(object):
## disconnect from previous view
if oldView is not None:
#print "disconnect:", self, oldView
- oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
- oldView.sigTransformChanged.disconnect(self.viewTransformChanged)
+ try:
+ oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
+ except TypeError:
+ pass
+
+ try:
+ oldView.sigTransformChanged.disconnect(self.viewTransformChanged)
+ except TypeError:
+ pass
+
self._connectedView = None
## connect to new view
@@ -449,4 +458,22 @@ class GraphicsItem(object):
view = self.getViewBox()
if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'):
view.itemBoundsChanged(self) ## inform view so it can update its range if it wants
-
\ No newline at end of file
+
+ def childrenShape(self):
+ """Return the union of the shapes of all descendants of this item in local coordinates."""
+ childs = self.allChildItems()
+ shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()]
+ return reduce(operator.add, shapes)
+
+ def allChildItems(self, root=None):
+ """Return list of the entire item tree descending from this item."""
+ if root is None:
+ root = self
+ tree = []
+ for ch in root.childItems():
+ tree.append(ch)
+ tree.extend(self.allChildItems(ch))
+ return tree
+
+
+
\ No newline at end of file
diff --git a/graphicsItems/GraphicsObject.py b/graphicsItems/GraphicsObject.py
index 4361d1e6..121a67ea 100644
--- a/graphicsItems/GraphicsObject.py
+++ b/graphicsItems/GraphicsObject.py
@@ -1,4 +1,6 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
+if not USE_PYSIDE:
+ import sip
from .GraphicsItem import GraphicsItem
__all__ = ['GraphicsObject']
@@ -20,4 +22,10 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
self._updateView()
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
self.informViewBoundsChanged()
+
+ ## workaround for pyqt bug:
+ ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html
+ if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem):
+ ret = sip.cast(ret, QtGui.QGraphicsItem)
+
return ret
diff --git a/graphicsItems/IsocurveItem.py b/graphicsItems/IsocurveItem.py
index ef09928b..01ef57b6 100644
--- a/graphicsItems/IsocurveItem.py
+++ b/graphicsItems/IsocurveItem.py
@@ -45,12 +45,12 @@ class IsocurveItem(GraphicsObject):
"""
Set the data/image to draw isocurves for.
- ============= ================================================================
+ ============= ========================================================================
**Arguments**
data A 2-dimensional ndarray.
level The cutoff value at which to draw the curve. If level is not specified,
- the previous level is used.
- ============= ================================================================
+ the previously set level is used.
+ ============= ========================================================================
"""
if level is None:
level = self.level
@@ -74,6 +74,12 @@ class IsocurveItem(GraphicsObject):
self.pen = fn.mkPen(*args, **kwargs)
self.update()
+ def setBrush(self, *args, **kwargs):
+ """Set the brush used to draw the isocurve. Arguments can be any that are valid
+ for :func:`mkBrush
`"""
+ self.brush = fn.mkBrush(*args, **kwargs)
+ self.update()
+
def updateLines(self, data, level):
##print "data:", data
@@ -88,22 +94,28 @@ class IsocurveItem(GraphicsObject):
self.setData(data, level)
def boundingRect(self):
- if self.path is None:
+ if self.data is None:
return QtCore.QRectF()
+ if self.path is None:
+ self.generatePath()
return self.path.boundingRect()
def generatePath(self):
- self.path = QtGui.QPainterPath()
if self.data is None:
+ self.path = None
return
- lines = fn.isocurve(self.data, self.level)
+ lines = fn.isocurve(self.data, self.level, connected=True, extendToEdge=True)
+ self.path = QtGui.QPainterPath()
for line in lines:
self.path.moveTo(*line[0])
- self.path.lineTo(*line[1])
+ for p in line[1:]:
+ self.path.lineTo(*p)
def paint(self, p, *args):
+ if self.data is None:
+ return
if self.path is None:
self.generatePath()
p.setPen(self.pen)
p.drawPath(self.path)
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py
index f0dcfb60..155330f1 100644
--- a/graphicsItems/ScatterPlotItem.py
+++ b/graphicsItems/ScatterPlotItem.py
@@ -233,7 +233,7 @@ class ScatterPlotItem(GraphicsObject):
self.bounds = [None, None] ## caches data bounds
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
- self.opts = {'pxMode': True, 'useCache': True} ## If useCache is False, symbols are re-drawn on every paint.
+ self.opts = {'pxMode': True, 'useCache': True, 'exportMode': False} ## If useCache is False, symbols are re-drawn on every paint.
self.setPen(200,200,200, update=False)
self.setBrush(100,100,150, update=False)
@@ -664,10 +664,14 @@ class ScatterPlotItem(GraphicsObject):
rect = QtCore.QRectF(y, x, h, w)
self.fragments.append(QtGui.QPainter.PixmapFragment.create(pos, rect))
+ def setExportMode(self, enabled, opts):
+ self.opts['exportMode'] = enabled
+
+
def paint(self, p, *args):
#p.setPen(fn.mkPen('r'))
#p.drawRect(self.boundingRect())
- if self.opts['pxMode']:
+ if self.opts['pxMode'] is True:
atlas = self.fragmentAtlas.getAtlas()
#arr = fn.imageToArray(atlas.toImage(), copy=True)
#if hasattr(self, 'lastAtlas'):
@@ -681,7 +685,7 @@ class ScatterPlotItem(GraphicsObject):
p.resetTransform()
- if not USE_PYSIDE and self.opts['useCache']:
+ if not USE_PYSIDE and self.opts['useCache'] and self.opts['exportMode'] is False:
p.drawPixmapFragments(self.fragments, atlas)
else:
for i in range(len(self.data)):
diff --git a/graphicsItems/TextItem.py b/graphicsItems/TextItem.py
index 5d84e23f..a85e919d 100644
--- a/graphicsItems/TextItem.py
+++ b/graphicsItems/TextItem.py
@@ -7,7 +7,7 @@ class TextItem(UIGraphicsItem):
"""
GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox).
"""
- def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None):
+ def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0):
"""
=========== =================================================================================
Arguments:
@@ -22,6 +22,12 @@ class TextItem(UIGraphicsItem):
*fill* A brush to use when filling within the border
=========== =================================================================================
"""
+
+ ## not working yet
+ #*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's
+ #transformation will be ignored)
+
+
UIGraphicsItem.__init__(self)
self.textItem = QtGui.QGraphicsTextItem()
self.lastTransform = None
@@ -33,6 +39,7 @@ class TextItem(UIGraphicsItem):
self.anchor = pg.Point(anchor)
self.fill = pg.mkBrush(fill)
self.border = pg.mkPen(border)
+ self.angle = angle
#self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport
def setText(self, text, color=(200,200,200)):
@@ -115,9 +122,11 @@ class TextItem(UIGraphicsItem):
#p.fillRect(tbr)
p.resetTransform()
- p.drawRect(tbr)
+ #p.drawRect(tbr)
p.translate(tbr.left(), tbr.top())
+ p.rotate(self.angle)
+ p.drawRect(QtCore.QRectF(0, 0, tbr.width(), tbr.height()))
self.textItem.paint(p, QtGui.QStyleOptionGraphicsItem(), None)
\ No newline at end of file
diff --git a/graphicsItems/UIGraphicsItem.py b/graphicsItems/UIGraphicsItem.py
index 2bcdd66e..19fda424 100644
--- a/graphicsItems/UIGraphicsItem.py
+++ b/graphicsItems/UIGraphicsItem.py
@@ -1,6 +1,8 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
import weakref
from .GraphicsObject import GraphicsObject
+if not USE_PYSIDE:
+ import sip
__all__ = ['UIGraphicsItem']
class UIGraphicsItem(GraphicsObject):
@@ -44,9 +46,12 @@ class UIGraphicsItem(GraphicsObject):
def itemChange(self, change, value):
ret = GraphicsObject.itemChange(self, change, value)
- #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: ## handled by GraphicsItem now.
- ##print "caught parent/scene change:", self.parentItem(), self.scene()
- #self.updateView()
+
+ ## workaround for pyqt bug:
+ ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html
+ if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem):
+ ret = sip.cast(ret, QtGui.QGraphicsItem)
+
if change == self.ItemScenePositionHasChanged:
self.setNewBounds()
return ret
diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py
index 0602939f..a1cfde89 100644
--- a/graphicsItems/ViewBox/ViewBox.py
+++ b/graphicsItems/ViewBox/ViewBox.py
@@ -111,6 +111,7 @@ class ViewBox(GraphicsWidget):
}
self._updatingRange = False ## Used to break recursive loops. See updateAutoRange.
+ self.locateGroup = None ## items displayed when using ViewBox.locate(item)
self.setFlag(self.ItemClipsChildrenToShape)
self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses
@@ -286,6 +287,12 @@ class ViewBox(GraphicsWidget):
self.scene().removeItem(item)
self.updateAutoRange()
+ def clear(self):
+ for i in self.addedItems[:]:
+ self.removeItem(i)
+ for ch in self.childGroup.childItems():
+ ch.setParent(None)
+
def resizeEvent(self, ev):
#self.setRange(self.range, padding=0)
#self.updateAutoRange()
@@ -1231,6 +1238,46 @@ class ViewBox(GraphicsWidget):
k.destroyed.disconnect()
except RuntimeError: ## signal is already disconnected.
pass
+
+ def locate(self, item, timeout=3.0, children=False):
+ """
+ Temporarily display the bounding rect of an item and lines connecting to the center of the view.
+ This is useful for determining the location of items that may be out of the range of the ViewBox.
+ if allChildren is True, then the bounding rect of all item's children will be shown instead.
+ """
+ self.clearLocate()
+ if item.scene() is not self.scene():
+ raise Exception("Item does not share a scene with this ViewBox.")
+ c = self.viewRect().center()
+ if children:
+ br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect()
+ else:
+ br = self.mapFromItemToView(item, item.boundingRect()).boundingRect()
+
+ g = ItemGroup()
+ g.setParentItem(self.childGroup)
+ self.locateGroup = g
+ g.box = QtGui.QGraphicsRectItem(br)
+ g.box.setParentItem(g)
+ g.lines = []
+ for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()):
+ line = QtGui.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y())
+ line.setParentItem(g)
+ g.lines.append(line)
+
+ for item in g.childItems():
+ item.setPen(fn.mkPen(color='y', width=3))
+ item.setZValue(1000000)
+
+ QtCore.QTimer.singleShot(timeout*1000, self.clearLocate)
+
+ def clearLocate(self):
+ if self.locateGroup is None:
+ return
+ self.scene().removeItem(self.locateGroup)
+ self.locateGroup = None
+
+
from .ViewBoxMenu import ViewBoxMenu
diff --git a/opengl/items/GLMeshItem.py b/opengl/items/GLMeshItem.py
index 5d98285f..4222c96b 100644
--- a/opengl/items/GLMeshItem.py
+++ b/opengl/items/GLMeshItem.py
@@ -158,7 +158,7 @@ class GLMeshItem(GLGraphicsItem):
if self.colors is None:
color = self.opts['color']
if isinstance(color, QtGui.QColor):
- glColor4f(*fn.glColor(color))
+ glColor4f(*pg.glColor(color))
else:
glColor4f(*color)
else:
diff --git a/python2_3.py b/python2_3.py
index c295dbfc..2182d3a1 100644
--- a/python2_3.py
+++ b/python2_3.py
@@ -53,6 +53,7 @@ if sys.version_info[0] == 3:
else:
return 0
builtins.cmp = cmp
+ builtins.xrange = range
#else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe
#import __builtin__
#__builtin__.asUnicode = asUnicode
diff --git a/reload.py b/reload.py
index 91801fe1..b9459073 100644
--- a/reload.py
+++ b/reload.py
@@ -35,6 +35,7 @@ def reloadAll(prefix=None, debug=False):
- if prefix is None, checks all loaded modules
"""
failed = []
+ changed = []
for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload
if not inspect.ismodule(mod):
continue
@@ -50,10 +51,11 @@ def reloadAll(prefix=None, debug=False):
## ignore if the .pyc is newer than the .py (or if there is no pyc or py)
py = os.path.splitext(mod.__file__)[0] + '.py'
pyc = py + 'c'
- if os.path.isfile(pyc) and os.path.isfile(py) and os.stat(pyc).st_mtime >= os.stat(py).st_mtime:
+ if py not in changed and os.path.isfile(pyc) and os.path.isfile(py) and os.stat(pyc).st_mtime >= os.stat(py).st_mtime:
#if debug:
#print "Ignoring module %s; unchanged" % str(mod)
continue
+ changed.append(py) ## keep track of which modules have changed to insure that duplicate-import modules get reloaded.
try:
reload(mod, debug=debug)
@@ -73,7 +75,7 @@ def reload(module, debug=False, lists=False, dicts=False):
- Requires that class and function names have not changed
"""
if debug:
- print("Reloading", module)
+ print("Reloading %s" % str(module))
## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison
oldDict = module.__dict__.copy()
@@ -158,7 +160,7 @@ def updateClass(old, new, debug):
if isinstance(ref, old) and ref.__class__ is old:
ref.__class__ = new
if debug:
- print(" Changed class for", safeStr(ref))
+ print(" Changed class for %s" % safeStr(ref))
elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__:
ind = ref.__bases__.index(old)
@@ -174,7 +176,7 @@ def updateClass(old, new, debug):
## (and I presume this may slow things down?)
ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:]
if debug:
- print(" Changed superclass for", safeStr(ref))
+ print(" Changed superclass for %s" % safeStr(ref))
#else:
#if debug:
#print " Ignoring reference", type(ref)
@@ -208,7 +210,7 @@ def updateClass(old, new, debug):
for attr in dir(new):
if not hasattr(old, attr):
if debug:
- print(" Adding missing attribute", attr)
+ print(" Adding missing attribute %s" % attr)
setattr(old, attr, getattr(new, attr))
## finally, update any previous versions still hanging around..
diff --git a/setup.py b/setup.py
index 9d56a0cf..31376ce5 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,11 @@ all_packages = ['.'.join(p) for p in subdirs]
setup(name='pyqtgraph',
version='',
description='Scientific Graphics and GUI Library for Python',
- long_description="PyQtGraph is a pure-python graphics and GUI library built on PyQt4 and numpy. It is intended for use in mathematics / scientific / engineering applications. Despite being written entirely in python, the library is very fast due to its heavy leverage of numpy for number crunching and Qt's GraphicsView framework for fast display.",
+ long_description="""\
+PyQtGraph is a pure-python graphics and GUI library built on PyQt4/PySide and numpy.
+
+It is intended for use in mathematics / scientific / engineering applications. Despite being written entirely in python, the library is very fast due to its heavy leverage of numpy for number crunching and Qt's GraphicsView framework for fast display.
+""",
license='MIT',
url='http://www.pyqtgraph.org',
author='Luke Campagnola',
@@ -17,5 +21,17 @@ setup(name='pyqtgraph',
packages=all_packages,
package_dir = {'pyqtgraph': '.'},
package_data={'pyqtgraph': ['graphicsItems/PlotItem/*.png']},
+ classifiers = [
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Development Status :: 4 - Beta",
+ "Environment :: Other Environment",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Scientific/Engineering :: Visualization",
+ "Topic :: Software Development :: User Interfaces",
+ ],
)