From accafcce368342a235ed3ca986aa595ac152d088 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 16 Feb 2015 11:04:58 -0500 Subject: [PATCH 1/2] WIP: adding new tests and fixing bugs in pg.interpolateArray --- pyqtgraph/functions.py | 17 +++++++----- pyqtgraph/graphicsItems/ROI.py | 20 ++++---------- pyqtgraph/tests/test_functions.py | 44 +++++++++++++++++++++++++++---- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 897a123d..eccb1f52 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -460,7 +460,7 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, ind = (Ellipsis,) + inds output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) else: - # map_coordinates expects the indexes as the first axis, whereas + # map_coordinates expects the indexes as the first axis, whereas # interpolateArray expects indexes at the last axis. tr = tuple(range(1,x.ndim)) + (0,) output = interpolateArray(data, x.transpose(tr)) @@ -483,7 +483,7 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, def interpolateArray(data, x, default=0.0): """ - N-dimensional interpolation similar scipy.ndimage.map_coordinates. + N-dimensional interpolation similar to scipy.ndimage.map_coordinates. This function returns linearly-interpolated values sampled from a regular grid of data. @@ -492,7 +492,7 @@ def interpolateArray(data, x, default=0.0): *x* is an array with (shape[-1] <= data.ndim) containing the locations within *data* to interpolate. - Returns array of shape (x.shape[:-1] + data.shape) + Returns array of shape (x.shape[:-1] + data.shape[x.shape[-1]:]) For example, assume we have the following 2D image data:: @@ -535,7 +535,7 @@ def interpolateArray(data, x, default=0.0): This is useful for interpolating from arrays of colors, vertexes, etc. """ - + print "x:\n", x.shape, x prof = debug.Profiler() nd = data.ndim @@ -552,7 +552,7 @@ def interpolateArray(data, x, default=0.0): for ax in range(md): mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1) # keep track of points that need to be set to default - totalMask &= mask + totalMask &= mask # ..and keep track of indexes that are out of bounds # (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out @@ -564,7 +564,7 @@ def interpolateArray(data, x, default=0.0): axisIndex[axisIndex >= data.shape[ax]] = 0 fieldInds.append(axisIndex) prof() - + print "fieldInds:\n", fieldInds # Get data values surrounding each requested point # fieldData[..., i] contains all 2**nd values needed to interpolate x[i] fieldData = data[tuple(fieldInds)] @@ -585,8 +585,11 @@ def interpolateArray(data, x, default=0.0): result = result.sum(axis=0) prof() - totalMask.shape = totalMask.shape + (1,) * (nd - md) + #totalMask.shape = totalMask.shape + (1,) * (nd - md) + print "mask:\n", totalMask.shape, totalMask + print "result:\n", result.shape, result result[~totalMask] = default + print "masked:\n", result.shape, result prof() return result diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 7707466a..4fdd439b 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -1061,8 +1061,8 @@ class ROI(GraphicsObject): =================== ==================================================== This method uses :func:`affineSlice ` to generate - the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to - pass to :func:`affineSlice `. + the slice from *data* and uses :func:`getAffineSliceParams ` + to determine the parameters to pass to :func:`affineSlice `. 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 @@ -1079,24 +1079,16 @@ 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] ## 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 def getAffineSliceParams(self, data, img, axes=(0,1)): """ - Returns the parameters needed to use :func:`affineSlice ` to - extract a subset of *data* using this ROI and *img* to specify the subset. + Returns the parameters needed to use :func:`affineSlice ` + (shape, vectors, origin) to extract a subset of *data* using this ROI + and *img* to specify the subset. See :func:`getArrayRegion ` for more information. """ @@ -1138,8 +1130,6 @@ class ROI(GraphicsObject): relativeTo['scale'] = relativeTo['size'] st['scale'] = st['size'] - - t1 = SRTTransform(relativeTo) t2 = SRTTransform(st) return t2/t1 diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py index f622dd87..e3fcbdcf 100644 --- a/pyqtgraph/tests/test_functions.py +++ b/pyqtgraph/tests/test_functions.py @@ -22,18 +22,39 @@ def testSolve3D(): def test_interpolateArray(): + def interpolateArray(data, x): + result = pg.interpolateArray(data, x) + assert result.shape == x.shape[:-1] + data.shape[x.shape[-1]:] + return result + data = np.array([[ 1., 2., 4. ], [ 10., 20., 40. ], [ 100., 200., 400.]]) + # test various x shapes + interpolateArray(data, np.ones((1,))) + interpolateArray(data, np.ones((2,))) + interpolateArray(data, np.ones((1, 1))) + interpolateArray(data, np.ones((1, 2))) + interpolateArray(data, np.ones((5, 1))) + interpolateArray(data, np.ones((5, 2))) + interpolateArray(data, np.ones((5, 5, 1))) + interpolateArray(data, np.ones((5, 5, 2))) + with pytest.raises(TypeError): + interpolateArray(data, np.ones((3,))) + with pytest.raises(TypeError): + interpolateArray(data, np.ones((1, 3,))) + with pytest.raises(TypeError): + interpolateArray(data, np.ones((5, 5, 3,))) + + x = np.array([[ 0.3, 0.6], [ 1. , 1. ], [ 0.5, 1. ], [ 0.5, 2.5], [ 10. , 10. ]]) - result = pg.interpolateArray(data, x) - + result = interpolateArray(data, x) #import scipy.ndimage #spresult = scipy.ndimage.map_coordinates(data, x.T, order=1) spresult = np.array([ 5.92, 20. , 11. , 0. , 0. ]) # generated with the above line @@ -44,9 +65,10 @@ def test_interpolateArray(): x = np.array([[ 0.3, 0], [ 0.3, 1], [ 0.3, 2]]) + r1 = interpolateArray(data, x) + x = np.array([0.3]) # should broadcast across axis 1 + r2 = interpolateArray(data, x) - r1 = pg.interpolateArray(data, x) - r2 = pg.interpolateArray(data, x[0,:1]) assert_array_almost_equal(r1, r2) @@ -54,13 +76,25 @@ def test_interpolateArray(): x = np.array([[[0.5, 0.5], [0.5, 1.0], [0.5, 1.5]], [[1.5, 0.5], [1.5, 1.0], [1.5, 1.5]]]) - r1 = pg.interpolateArray(data, x) + r1 = interpolateArray(data, x) #r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=1) r2 = np.array([[ 8.25, 11. , 16.5 ], # generated with the above line [ 82.5 , 110. , 165. ]]) assert_array_almost_equal(r1, r2) + + # test interpolate where data.ndim > x.shape[1] + + data = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]) # 2x2x3 + x = np.array([[1, 1], [0, 0.5], [5, 5]]) + + r1 = interpolateArray(data, x) + assert np.all(r1[0] == data[1, 1]) + assert np.all(r1[1] == 0.5 * (data[0, 0] + data[0, 1])) + assert np.all(r1[2] == 0) + + def test_subArray(): a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) From 4066c7c76ef3b0a0b0ea6eec519c60c7c9afa961 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 16 Feb 2015 11:23:52 -0500 Subject: [PATCH 2/2] tests working --- pyqtgraph/functions.py | 19 ++++++++++--------- pyqtgraph/tests/test_functions.py | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index eccb1f52..3b3e1156 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -535,11 +535,12 @@ def interpolateArray(data, x, default=0.0): This is useful for interpolating from arrays of colors, vertexes, etc. """ - print "x:\n", x.shape, x prof = debug.Profiler() nd = data.ndim md = x.shape[-1] + if md > nd: + raise TypeError("x.shape[-1] must be less than or equal to data.ndim") # First we generate arrays of indexes that are needed to # extract the data surrounding each point @@ -559,14 +560,12 @@ def interpolateArray(data, x, default=0.0): # of bounds, but the interpolation will work anyway) mask &= (xmax[...,ax] < data.shape[ax]) axisIndex = indexes[...,ax][fields[ax]] - #axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape) axisIndex[axisIndex < 0] = 0 axisIndex[axisIndex >= data.shape[ax]] = 0 fieldInds.append(axisIndex) prof() - print "fieldInds:\n", fieldInds + # Get data values surrounding each requested point - # fieldData[..., i] contains all 2**nd values needed to interpolate x[i] fieldData = data[tuple(fieldInds)] prof() @@ -585,11 +584,13 @@ def interpolateArray(data, x, default=0.0): result = result.sum(axis=0) prof() - #totalMask.shape = totalMask.shape + (1,) * (nd - md) - print "mask:\n", totalMask.shape, totalMask - print "result:\n", result.shape, result - result[~totalMask] = default - print "masked:\n", result.shape, result + + if totalMask.ndim > 0: + result[~totalMask] = default + else: + if totalMask is False: + result[:] = default + prof() return result diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py index e3fcbdcf..4ef2daf0 100644 --- a/pyqtgraph/tests/test_functions.py +++ b/pyqtgraph/tests/test_functions.py @@ -1,6 +1,7 @@ import pyqtgraph as pg import numpy as np from numpy.testing import assert_array_almost_equal, assert_almost_equal +import pytest np.random.seed(12345)