Quick interpolation of grid data

I have a large 3d np.ndarray of data representing a physical variable sampled by the volume in the regular grid (as in the value in the array [0,0,0] represents the value in physical coordinates (0, 0,0)).

I would like to move to a smaller grid interval by interpolating the data in a coarse grid. I am currently using linear interpolation of scipy griddata, but it is pretty slow (~ 90 seconds for a 20x20x20 array). This is a bit overrated for my purposes, allowing random sampling of volume data. Is there anything that can use my data at regular intervals and the fact that there is only a limited set of specific points to which I want to interpolate?

+18
python numpy scipy interpolation
Jun 07 '13 at 12:12
source share
2 answers

Sure! There are two options that do different things, but both use a regular source data structure.

The first is scipy.ndimage.zoom . If you just want to create a denser regular grid based on the interpolation of the source data, this is the way to go.

Second scipy.ndimage.map_coordinates . If you want to interpolate several (or many) arbitrary points in your data, but still use the regular nature of the source data (for example, you don't need a quadrant), this is the way to go.




Array Scaling ( scipy.ndimage.zoom )

As a quick example (this will use cubic interpolation. Use order=1 for bilinear, order=0 for nearest, etc.):

 import numpy as np import scipy.ndimage as ndimage data = np.arange(9).reshape(3,3) print 'Original:\n', data print 'Zoomed by 2x:\n', ndimage.zoom(data, 2) 

This gives:

 Original: [[0 1 2] [3 4 5] [6 7 8]] Zoomed by 2x: [[0 0 1 1 2 2] [1 1 1 2 2 3] [2 2 3 3 4 4] [4 4 5 5 6 6] [5 6 6 7 7 7] [6 6 7 7 8 8]] 

This also works for 3D (and nD) arrays. However, keep in mind that if you zoom in 2x, for example, you will scale along all axes.

 data = np.arange(27).reshape(3,3,3) print 'Original:\n', data print 'Zoomed by 2x gives an array of shape:', ndimage.zoom(data, 2).shape 

This gives:

 Original: [[[ 0 1 2] [ 3 4 5] [ 6 7 8]] [[ 9 10 11] [12 13 14] [15 16 17]] [[18 19 20] [21 22 23] [24 25 26]]] Zoomed by 2x gives an array of shape: (6, 6, 6) 

If you have something like a 3-way RGB image that you want to enlarge, you can do this by specifying a sequence of tuples as a zoom factor:

 print 'Zoomed by 2x along the last two axes:' print ndimage.zoom(data, (1, 2, 2)) 

This gives:

 Zoomed by 2x along the last two axes: [[[ 0 0 1 1 2 2] [ 1 1 1 2 2 3] [ 2 2 3 3 4 4] [ 4 4 5 5 6 6] [ 5 6 6 7 7 7] [ 6 6 7 7 8 8]] [[ 9 9 10 10 11 11] [10 10 10 11 11 12] [11 11 12 12 13 13] [13 13 14 14 15 15] [14 15 15 16 16 16] [15 15 16 16 17 17]] [[18 18 19 19 20 20] [19 19 19 20 20 21] [20 20 21 21 22 22] [22 22 23 23 24 24] [23 24 24 25 25 25] [24 24 25 25 26 26]]] 



Arbitrary interpolation of data with a regular grid using map_coordinates

The first thing about map_coordinates is that it works in pixel coordinates (for example, just like you indexed an array, but values ​​can be floats). From your description, this is exactly what you want, but often confuses people. For example, if you have the "real-world" x, y, z coordinates, you will need to convert them to the "pixel" coordinates based on the index.

In any case, let's say we wanted to interpolate the value in the original array at positions 1.2, 0.3, 1.4.

If you think about this in the earlier case of the RGB image, the first coordinate corresponds to the "strip", the second to the "row", and the last to the "column". Which order corresponds to the one that completely depends on how you decide to structure your data, but I will use them as the coordinates "z, y, x", as this simplifies the visualization of the comparison with the print array.

 import numpy as np import scipy.ndimage as ndimage data = np.arange(27).reshape(3,3,3) print 'Original:\n', data print 'Sampled at 1.2, 0.3, 1.4:' print ndimage.map_coordinates(data, [[1.2], [0.3], [1.4]]) 

This gives:

 Original: [[[ 0 1 2] [ 3 4 5] [ 6 7 8]] [[ 9 10 11] [12 13 14] [15 16 17]] [[18 19 20] [21 22 23] [24 25 26]]] Sampled at 1.2, 0.3, 1.4: [14] 

Again, this is the default cubic interpolation. Use order kwarg to control the type of interpolation.

It is worth noting here that all scipy.ndimage operations save the dtype of the original array. If you want to get floating point results, you will need to distinguish the original array as a float:

 In [74]: ndimage.map_coordinates(data.astype(float), [[1.2], [0.3], [1.4]]) Out[74]: array([ 13.5965]) 

Another thing you may notice is that the format of the interpolated coordinates is rather cumbersome for a single point (for example, it expects a 3xN array instead of an Nx3 array). However, this is possibly better when you have a sequence of coordinates. For example, consider the case of sampling along a line that passes through a β€œcube” of data:

 xi = np.linspace(0, 2, 10) yi = 0.8 * xi zi = 1.2 * xi print ndimage.map_coordinates(data, [zi, yi, xi]) 

This gives:

 [ 0 1 4 8 12 17 21 24 0 0] 

It is also a good place to mention how boundary conditions are handled. By default, everything outside the array is set to 0. Thus, the last two values ​​in the sequence are 0 . (i.e., zi > 2 for the last two elements).

If we wanted the points outside the array to be, say -999 (we cannot use nan , since it is an integer array. If you want nan , you will need to reset to float.):

 In [75]: ndimage.map_coordinates(data, [zi, yi, xi], cval=-999) Out[75]: array([ 0, 1, 4, 8, 12, 17, 21, 24, -999, -999]) 

If we wanted to return the closest value to points outside the array, we would do:

 In [76]: ndimage.map_coordinates(data, [zi, yi, xi], mode='nearest') Out[76]: array([ 0, 1, 4, 8, 12, 17, 21, 24, 25, 25]) 

You can also use "reflect" and "wrap" as border modes in addition to the "nearest" and the default is "constant" . They are pretty clear, but try experimenting a bit if you're confused.

For example, let interpolate a line along the first line of the first strip in the array, which extends twice the distance of the array:

 xi = np.linspace(0, 5, 10) yi, zi = np.zeros_like(xi), np.zeros_like(xi) 

Default value:

 In [77]: ndimage.map_coordinates(data, [zi, yi, xi]) Out[77]: array([0, 0, 1, 2, 0, 0, 0, 0, 0, 0]) 

Compare this to:

 In [78]: ndimage.map_coordinates(data, [zi, yi, xi], mode='reflect') Out[78]: array([0, 0, 1, 2, 2, 1, 2, 1, 0, 0]) In [78]: ndimage.map_coordinates(data, [zi, yi, xi], mode='wrap') Out[78]: array([0, 0, 1, 2, 0, 1, 1, 2, 0, 1]) 

Hope this clears things up a bit!

+31
Jun 07 '13 at 12:24
source share

Great answer Joe. Based on his assumption, I created a regulargrid package ( https://pypi.python.org/pypi/regulargrid/ , source at https://github.com/JohannesBuchner/regulargrid )

It provides support for n-dimensional Cartesian grids (as needed here) using the very fast scipy.ndimage.map_coordinates for arbitrary coordinate scales.

+7
Sep 20 '13 at 9:59
source share



All Articles