Commit 39fa59b6 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

ImageView fix: display correct coordinates in ROI plot for scaled, single-frame images

parents a41d330c c686395e
......@@ -18,6 +18,7 @@ import sys, os
# documentation root, use os.path.abspath to make it absolute, like shown here.
path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(path, '..', '..', '..'))
print sys.path
# -- General configuration -----------------------------------------------------
......
......@@ -358,28 +358,36 @@ def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0)
def affineSlice(data, shape, origin, vectors, axes, **kargs):
def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
"""
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets.
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this).
For a graphical interface to this function, see :func:`ROI.getArrayRegion`
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
============== ====================================================================================================
Arguments:
*data* (ndarray) the original dataset
*shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape))
*origin* the location in the original dataset that will become the origin of the sliced data.
*vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same
length as *axes*. If the vectors are not unit length, the result will be scaled relative to the
original data. If the vectors are not orthogonal, the result will be sheared relative to the
original data.
*axes* The axes in the original dataset which correspond to the slice *vectors*
*order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates
for more information.
*returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select
values from the original dataset.
*All extra keyword arguments are passed to scipy.ndimage.map_coordinates.*
--------------------------------------------------------------------------------------------------------------------
============== ====================================================================================================
| *data* (ndarray): the original dataset
| *shape*: the shape of the slice to take (Note the return value may have more dimensions than len(shape))
| *origin*: the location in the original dataset that will become the origin in the sliced data.
| *vectors*: list of unit vectors which point in the direction of the slice axes
* each vector must have the same length as *axes*
* If the vectors are not unit length, the result will be scaled.
* If the vectors are not orthogonal, the result will be sheared.
*axes*: the axes in the original dataset which correspond to the slice *vectors*
Note the following must be true:
All extra keyword arguments are passed to scipy.ndimage.map_coordinates
| len(shape) == len(vectors)
| len(origin) == len(axes) == len(vectors[i])
Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes
......@@ -392,10 +400,6 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
Note the following must be true:
| len(shape) == len(vectors)
| len(origin) == len(axes) == len(vectors[0])
"""
# sanity check
......@@ -437,7 +441,7 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
for inds in np.ndindex(*extraShape):
ind = (Ellipsis,) + inds
#print data[ind].shape, x.shape, output[ind].shape, output.shape
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, **kargs)
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
tr = list(range(output.ndim))
trb = []
......@@ -448,9 +452,18 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
tr2 = tuple(trb+tr)
## Untranspose array before returning
return output.transpose(tr2)
output = output.transpose(tr2)
if returnCoords:
return (output, x)
else:
return output
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()]])
def solve3DTransform(points1, points2):
"""
Find a 3D transformation matrix that maps points1 onto points2
......
......@@ -832,35 +832,34 @@ class ROI(GraphicsObject):
else:
return bounds, tr
def getArrayRegion(self, data, img, axes=(0,1)):
"""Use the position of this ROI relative to an imageItem to pull a slice from an array."""
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
"""Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array.
This method uses :func:`affineSlice <pyqtgraph.affineSlice>` to generate
the slice from *data* and uses :func:`getAffineSliceParams <pyqtgraph.ROI.getAffineSliceParams>` to determine the parameters to
pass to :func:`affineSlice <pyqtgraph.affineSlice>`.
shape = self.state['size']
If *returnMappedCoords* is True, then the method returns a tuple (result, coords)
such that coords is the set of coordinates used to interpolate values from the original
data, mapped into the parent coordinate system of the image. This is useful, when slicing
data from images that have been transformed, for determining the location of each value
in the sliced data.
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
## vx and vy point in the directions of the slice axes, but must be scaled properly
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
pxLen = img.width() / float(data.shape[axes[0]])
sx = pxLen / lvx
sy = pxLen / lvy
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
shape = self.state['size']
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
origin = (origin.x(), origin.y())
#print "shape", shape, "vectors", vectors, "origin", origin
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1)
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
"""
shape, vectors, origin = self.getAffineSliceParams(data, img, axes)
if not returnMappedCoords:
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
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)
return result, mapped
### transpose data so x and y are the first 2 axes
#trAx = range(0, data.ndim)
#trAx.remove(axes[0])
......@@ -959,6 +958,37 @@ class ROI(GraphicsObject):
### Untranspose array before returning
#return arr5.transpose(tr2)
def getAffineSliceParams(self, data, img, axes=(0.1)):
"""
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>` to
extract a subset of *data* using this ROI and *img* to specify the subset.
See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
"""
shape = self.state['size']
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
## vx and vy point in the directions of the slice axes, but must be scaled properly
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
pxLen = img.width() / float(data.shape[axes[0]])
#img.width is number of pixels or width of item?
#need pxWidth and pxHeight instead of pxLen ?
sx = pxLen / lvx
sy = pxLen / lvy
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
shape = self.state['size']
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
origin = (origin.x(), origin.y())
return shape, vectors, origin
def getGlobalTransform(self, relativeTo=None):
"""Return global transformation (rotation angle+translation) required to move
from relative state to current state. If relative state isn't specified,
......
......@@ -38,7 +38,7 @@ from pyqtgraph.SignalProxy import SignalProxy
class PlotROI(ROI):
def __init__(self, size):
ROI.__init__(self, pos=[0,0], size=size, scaleSnap=True, translateSnap=True)
ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True)
self.addScaleHandle([1, 1], [0, 0])
self.addRotateHandle([0, 0], [0.5, 0.5])
......@@ -531,14 +531,18 @@ class ImageView(QtGui.QWidget):
axes = (1, 2)
else:
return
data = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes)
data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True)
if data is not None:
while data.ndim > 1:
data = data.mean(axis=1)
if image.ndim == 3:
self.roiCurve.setData(y=data, x=self.tVals)
else:
self.roiCurve.setData(y=data, x=list(range(len(data))))
while coords.ndim > 2:
coords = coords[:,:,0]
coords = coords - coords[:,0,np.newaxis]
xvals = (coords**2).sum(axis=0) ** 0.5
self.roiCurve.setData(y=data, x=xvals)
#self.ui.roiPlot.replot()
......
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