Added pure-python implementation of scipy.ndimage.map_coordinates
This commit is contained in:
parent
1eac666d02
commit
72a3902a18
@ -372,7 +372,7 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
"""
|
"""
|
||||||
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.
|
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 original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this).
|
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 if it is available (see the scipy documentation for more information about this). If scipy is not available, then a slower implementation of map_coordinates is used.
|
||||||
|
|
||||||
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
|
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
|
||||||
|
|
||||||
@ -413,8 +413,9 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import scipy.ndimage
|
import scipy.ndimage
|
||||||
|
have_scipy = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception("This function requires the scipy library, but it does not appear to be importable.")
|
have_scipy = False
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if len(shape) != len(vectors):
|
if len(shape) != len(vectors):
|
||||||
@ -436,7 +437,6 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
#print "tr1:", tr1
|
#print "tr1:", tr1
|
||||||
## dims are now [(slice axes), (other axes)]
|
## dims are now [(slice axes), (other axes)]
|
||||||
|
|
||||||
|
|
||||||
## make sure vectors are arrays
|
## make sure vectors are arrays
|
||||||
if not isinstance(vectors, np.ndarray):
|
if not isinstance(vectors, np.ndarray):
|
||||||
vectors = np.array(vectors)
|
vectors = np.array(vectors)
|
||||||
@ -456,8 +456,10 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
|
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
|
||||||
for inds in np.ndindex(*extraShape):
|
for inds in np.ndindex(*extraShape):
|
||||||
ind = (Ellipsis,) + inds
|
ind = (Ellipsis,) + inds
|
||||||
#print data[ind].shape, x.shape, output[ind].shape, output.shape
|
if have_scipy:
|
||||||
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
|
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
|
||||||
|
else:
|
||||||
|
output[ind] = mapCoordinates(data[ind], x.T)
|
||||||
|
|
||||||
tr = list(range(output.ndim))
|
tr = list(range(output.ndim))
|
||||||
trb = []
|
trb = []
|
||||||
@ -474,6 +476,54 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
else:
|
else:
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def mapCoordinates(data, x, default=0.0):
|
||||||
|
"""
|
||||||
|
Pure-python alternative to scipy.ndimage.map_coordinates
|
||||||
|
|
||||||
|
*data* is an array of any shape.
|
||||||
|
*x* is an array with shape[-1] == data.ndim
|
||||||
|
|
||||||
|
Returns array of shape (x.shape[:-1] + data.shape)
|
||||||
|
"""
|
||||||
|
result = np.empty(x.shape[:-1] + data.shape, dtype=data.dtype)
|
||||||
|
nd = data.ndim
|
||||||
|
|
||||||
|
# First we generate arrays of indexes that are needed to
|
||||||
|
# extract the data surrounding each point
|
||||||
|
fields = np.mgrid[(slice(0,2),) * nd]
|
||||||
|
xmin = np.floor(x).astype(int)
|
||||||
|
xmax = xmin + 1
|
||||||
|
indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]])
|
||||||
|
fieldInds = []
|
||||||
|
totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes
|
||||||
|
for ax in range(nd):
|
||||||
|
mask = (xmin[...,ax] >= 0) & (xmax[...,ax] < data.shape[ax])
|
||||||
|
totalMask &= mask
|
||||||
|
axisIndex = indexes[...,ax][fields[ax]]
|
||||||
|
axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape)
|
||||||
|
axisIndex *= axisMask
|
||||||
|
fieldInds.append(axisIndex)
|
||||||
|
|
||||||
|
# Get data values surrounding each requested point
|
||||||
|
# fieldData[..., i] contains all 2**nd values needed to interpolate x[i]
|
||||||
|
fieldData = data[tuple(fieldInds)]
|
||||||
|
|
||||||
|
## Interpolate
|
||||||
|
s = np.empty((nd,) + fieldData.shape, dtype=float)
|
||||||
|
dx = x - xmin
|
||||||
|
# reshape fields for arithmetic against dx
|
||||||
|
for ax in range(nd):
|
||||||
|
f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1))
|
||||||
|
s[ax] = f1 * dx[:,ax] + (1-f1) * (1-dx[:,ax])
|
||||||
|
s = np.product(s, axis=0)
|
||||||
|
result = fieldData * s
|
||||||
|
for i in range(nd):
|
||||||
|
result = result.sum(axis=0)
|
||||||
|
|
||||||
|
result[~totalMask] = default
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def transformToArray(tr):
|
def transformToArray(tr):
|
||||||
"""
|
"""
|
||||||
Given a QTransform, return a 3x3 numpy array.
|
Given a QTransform, return a 3x3 numpy array.
|
||||||
|
@ -1083,105 +1083,6 @@ class ROI(GraphicsObject):
|
|||||||
mapped = fn.transformCoordinates(img.transform(), coords)
|
mapped = fn.transformCoordinates(img.transform(), coords)
|
||||||
return result, mapped
|
return result, mapped
|
||||||
|
|
||||||
|
|
||||||
### transpose data so x and y are the first 2 axes
|
|
||||||
#trAx = range(0, data.ndim)
|
|
||||||
#trAx.remove(axes[0])
|
|
||||||
#trAx.remove(axes[1])
|
|
||||||
#tr1 = tuple(axes) + tuple(trAx)
|
|
||||||
#arr = data.transpose(tr1)
|
|
||||||
|
|
||||||
### Determine the minimal area of the data we will need
|
|
||||||
#(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
|
|
||||||
|
|
||||||
### Pad data boundaries by 1px if possible
|
|
||||||
#dataBounds = (
|
|
||||||
#(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
|
|
||||||
#(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
|
|
||||||
#)
|
|
||||||
|
|
||||||
### Extract minimal data from array
|
|
||||||
#arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
|
|
||||||
|
|
||||||
### Update roiDataTransform to reflect this extraction
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
|
|
||||||
#### (roiDataTransform now maps from ROI coords to extracted data coords)
|
|
||||||
|
|
||||||
|
|
||||||
### Rotate array
|
|
||||||
#if abs(self.state['angle']) > 1e-5:
|
|
||||||
#arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
|
|
||||||
|
|
||||||
### update data transforms to reflect this rotation
|
|
||||||
#rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
|
|
||||||
#roiDataTransform *= rot
|
|
||||||
|
|
||||||
### The rotation also causes a shift which must be accounted for:
|
|
||||||
#dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
|
|
||||||
#rotBound = rot.mapRect(dataBound)
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
|
|
||||||
|
|
||||||
#else:
|
|
||||||
#arr2 = arr1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Shift off partial pixels
|
|
||||||
## 1. map ROI into current data space
|
|
||||||
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
|
|
||||||
|
|
||||||
## 2. Determine amount to shift data
|
|
||||||
#shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
|
|
||||||
#if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
|
|
||||||
## 3. pad array with 0s before shifting
|
|
||||||
#arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
|
|
||||||
#arr2a[1:-1, 1:-1] = arr2
|
|
||||||
|
|
||||||
## 4. shift array and udpate transforms
|
|
||||||
#arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
|
|
||||||
#roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
|
|
||||||
#else:
|
|
||||||
#arr3 = arr2
|
|
||||||
|
|
||||||
|
|
||||||
#### Extract needed region from rotated/shifted array
|
|
||||||
## 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
|
|
||||||
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
|
|
||||||
##print self, roiBounds.height()
|
|
||||||
##import traceback
|
|
||||||
##traceback.print_stack()
|
|
||||||
|
|
||||||
#roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
|
|
||||||
|
|
||||||
##2. intersect ROI with data bounds
|
|
||||||
#dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
|
|
||||||
|
|
||||||
##3. Extract data from array
|
|
||||||
#db = dataBounds
|
|
||||||
#bounds = (
|
|
||||||
#(db.left(), db.right()+1),
|
|
||||||
#(db.top(), db.bottom()+1)
|
|
||||||
#)
|
|
||||||
#arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
|
|
||||||
|
|
||||||
#### Create zero array in size of ROI
|
|
||||||
#arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
|
|
||||||
|
|
||||||
### Fill array with ROI data
|
|
||||||
#orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
|
|
||||||
#subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
|
|
||||||
#subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
|
|
||||||
|
|
||||||
|
|
||||||
### figure out the reverse transpose order
|
|
||||||
#tr2 = np.array(tr1)
|
|
||||||
#for i in range(0, len(tr2)):
|
|
||||||
#tr2[tr1[i]] = i
|
|
||||||
#tr2 = tuple(tr2)
|
|
||||||
|
|
||||||
### Untranspose array before returning
|
|
||||||
#return arr5.transpose(tr2)
|
|
||||||
|
|
||||||
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
||||||
"""
|
"""
|
||||||
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>` to
|
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>` to
|
||||||
|
@ -19,3 +19,24 @@ def testSolve3D():
|
|||||||
tr2 = pg.solve3DTransform(p1, p2)
|
tr2 = pg.solve3DTransform(p1, p2)
|
||||||
|
|
||||||
assert_array_almost_equal(tr[:3], tr2[:3])
|
assert_array_almost_equal(tr[:3], tr2[:3])
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapCoordinates():
|
||||||
|
data = np.array([[ 0., 1., 2.],
|
||||||
|
[ 2., 3., 5.],
|
||||||
|
[ 7., 7., 4.]])
|
||||||
|
|
||||||
|
x = np.array([[ 0.3, 0.6],
|
||||||
|
[ 1. , 1. ],
|
||||||
|
[ 0.5, 1. ],
|
||||||
|
[ 0.5, 2.5],
|
||||||
|
[ 10. , 10. ]])
|
||||||
|
|
||||||
|
result = pg.mapCoordinates(data, x)
|
||||||
|
|
||||||
|
import scipy.ndimage
|
||||||
|
spresult = scipy.ndimage.map_coordinates(data, x.T, order=1)
|
||||||
|
|
||||||
|
assert_array_almost_equal(result, spresult)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user