Commit c2f0bebe authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Added functions.transformCoordinates() for mapping numpy arrays of coordinates...

Added functions.transformCoordinates() for mapping numpy arrays of coordinates from QTransform and QMatrix4x4

Minor updates:
- fixed SRTTransform3D.matrix()
- ViewBox fix: updateAutoRange leaves unused axes completely unchanged
- documentation updates
parent 27c90c5d
......@@ -201,11 +201,11 @@ class SRTTransform3D(QtGui.QMatrix4x4):
def matrix(self, nd=3):
if nd == 3:
return np.array(self.copyDataTo())
return np.array(self.copyDataTo()).reshape(4,4)
elif nd == 2:
m = np.array(self.copyDataTo())
m = np.array(self.copyDataTo()).reshape(4,4)
m[2] = m[3]
m[:,2] = n[:,3]
m[:,2] = m[:,3]
return m[:3,:3]
else:
raise Exception("Argument 'nd' must be 2 or 3")
......
......@@ -461,9 +461,75 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
def transformToArray(tr):
"""
Given a QTransform, return a 3x3 numpy array.
"""
return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]])
Given a QMatrix4x4, return a 4x4 numpy array.
Example: map an array of x,y coordinates through a transform::
## coordinates to map are (1,5), (2,6), (3,7), and (4,8)
coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work
## Make an example transform
tr = QtGui.QTransform()
tr.translate(3,4)
tr.scale(2, 0.1)
## convert to array
m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation
## map coordinates through transform
mapped = np.dot(m, coords)
"""
#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
## a transformation matrix. However, with QTransform these values appear at m31 and m32.
## So the correct interpretation is transposed:
if isinstance(tr, QtGui.QTransform):
return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]])
elif isinstance(tr, QtGui.QMatrix4x4):
return np.array(tr.copyDataTo()).reshape(4,4)
else:
raise Exception("Transform argument must be either QTransform or QMatrix4x4.")
def transformCoordinates(tr, coords):
"""
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.
"""
nd = coords.shape[0]
m = transformToArray(tr)
m = m[:m.shape[0]-1] # remove perspective
## If coords are 3D and tr is 2D, assume no change for Z axis
if m.shape == (2,3) and nd == 3:
m2 = np.zeros((3,4))
m2[:2, :2] = m[:2,:2]
m2[:2, 3] = m[:2,2]
m2[2,2] = 1
m = m2
## if coords are 2D and tr is 3D, ignore Z axis
if m.shape == (3,4) and nd == 2:
m2 = np.empty((2,3))
m2[:,:2] = m[:2,:2]
m2[:,2] = m[:2,3]
m = m2
## reshape tr and coords to prepare for multiplication
m = m.reshape(m.shape + (1,)*(coords.ndim-1))
coords = coords[np.newaxis, ...]
# separate scale/rotate and translation
translate = m[:,-1]
m = m[:, :-1]
## map coordinates and return
mapped = (m*coords).sum(axis=0) ## apply scale/rotate
mapped += translate
return mapped
def solve3DTransform(points1, points2):
"""
Find a 3D transformation matrix that maps points1 onto points2
......
......@@ -854,9 +854,18 @@ class ROI(GraphicsObject):
else:
kwds['returnCoords'] = True
result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
tr = fn.transformToArray(img.transform())[:,:2].reshape((3, 2) + (1,)*(coords.ndim-1))
coords = coords[np.newaxis, ...]
mapped = (tr*coords).sum(axis=0)
#tr = fn.transformToArray(img.transform())[:2] ## remove perspective transform values
### separate translation from scale/rotate
#translate = tr[:,2]
#tr = tr[:,:2]
#tr = tr.reshape((2,2) + (1,)*(coords.ndim-1))
#coords = coords[np.newaxis, ...]
### map coordinates and return
#mapped = (tr*coords).sum(axis=0) ## apply scale/rotate
#mapped += translate.reshape((2,1,1))
mapped = fn.transformCoordinates(img.transform(), coords)
return result, mapped
......
......@@ -538,6 +538,7 @@ class ViewBox(GraphicsWidget):
if self.state['autoVisibleOnly'][0] is True:
order = [1,0]
args = {}
for ax in order:
if self.state['autoRange'][ax] is False:
continue
......@@ -563,6 +564,7 @@ class ViewBox(GraphicsWidget):
targetRect[0][0] = childRect.left()
targetRect[0][1] = childRect.right()
args['xRange'] = targetRect[0]
else:
## Make corrections to Y range
if self.state['autoPan'][1]:
......@@ -576,8 +578,11 @@ class ViewBox(GraphicsWidget):
targetRect[1][0] = childRect.top()
targetRect[1][1] = childRect.bottom()
self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
args['yRange'] = targetRect[1]
args['padding'] = 0
args['disableAutoRange'] = False
#self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
self.setRange(**args)
def setXLink(self, view):
"""Link this view's X axis to another view. (see LinkView)"""
......@@ -1213,7 +1218,8 @@ class ViewBox(GraphicsWidget):
@staticmethod
def forgetView(vid, name):
if ViewBox is None: ## can happen as python is shutting down
return
## Called with ID and name of view (the view itself is no longer available)
for v in ViewBox.AllViews.iterkeys():
if id(v) == vid:
......
......@@ -614,7 +614,8 @@ class Parameter(QtCore.QObject):
about to be made to the tree and only one change signal should be
emitted at the end.
Example:
Example::
with param.treeChangeBlocker():
param.addChild(...)
param.removeChild(...)
......@@ -638,11 +639,14 @@ class Parameter(QtCore.QObject):
def treeStateChanged(self, param, changes):
"""
Called when the state of any sub-parameter has changed.
========== ================================================================
Arguments:
param: the immediate child whose tree state has changed.
note that the change may have originated from a grandchild.
changes: list of tuples describing all changes that have been made
in this event: (param, changeDescr, data)
param The immediate child whose tree state has changed.
note that the change may have originated from a grandchild.
changes List of tuples describing all changes that have been made
in this event: (param, changeDescr, data)
========== ================================================================
This function can be extended to react to tree state changes.
"""
......@@ -668,4 +672,4 @@ class SignalBlocker:
self.exitFn()
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment