4

What I want to do is rather simple but I havent found a straightforward approach thus far:

I have a 3D rectilinear grid with float values (therefore 3 coordinate axes -1D numpy arrays- for the centers of the grid cells and a 3D numpy array with the corresponding shape with a value for each cell center) and I want to interpolate (or you may call it subsample) this entire array to a subsampled array (e.g. size factor of 5) with linear interpolation. All the approaches I've seen this far involve 2D and then 1D interpolation or VTK tricks which Id rather not use (portability).

Could someone suggest an approach that would be the equivalent of taking 5x5x5 cells at the same time in the 3D array, averaging and returning an array 5times smaller in each direction?

Thank you in advance for any suggestions

EDIT: Here's what the data looks like, 'd' is a 3D array representing a 3D grid of cells. Each cell has a scalar float value (pressure in my case) and 'x','y' and 'z' are three 1D arrays containing the spatial coordinates of the cells of every cell (see the shapes and how the 'x' array looks like)

In [42]: x.shape
Out[42]: (181L,)

In [43]: y.shape
Out[43]: (181L,)

In [44]: z.shape
Out[44]: (421L,)

In [45]: d.shape
Out[45]: (181L, 181L, 421L)

In [46]: x
Out[46]: 
array([-0.410607  , -0.3927568 , -0.37780656, -0.36527296, -0.35475321,
       -0.34591168, -0.33846866, -0.33219107, -0.32688467, -0.3223876 ,
        ...
        0.34591168,  0.35475321,  0.36527296,  0.37780656,  0.3927568 ,
        0.410607  ])

What I want to do is create a 3D array with lets say a shape of 90x90x210 (roughly downsize by a factor of 2) by first subsampling the coordinates from the axes on arrays with the above dimensions and then 'interpolating' the 3D data to that array. Im not sure whether 'interpolating' is the right term though. Downsampling? Averaging? Here's an 2D slice of the data: density maps

somada141
  • 1,274
  • 2
  • 18
  • 25
  • You might be looking for [scipy.interpolate.griddata](http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html#scipy.interpolate.griddata) (if you don't mind adding a numpy/scipy dependency). – unutbu Apr 01 '13 at 18:31
  • @unutbu: that was my first idea as well: - Create an mgrid with the original axes - Create 1D interpolations of each axis (e.g factor 5 down) - Create an mgrid with the new axes - Use the griddata However I cant seem to find a way to create a non-uniform (my data is rectilinear but the axes have different steps) mgrid. Could you point me? – somada141 Apr 01 '13 at 19:21
  • I confess to being a bit confused about what your data actually looks like. Could you give an example of the data and where you want to interpolate? – unutbu Apr 01 '13 at 19:55
  • I tried to edit the post a bit, clarifying the whole thing. Essentially I want to get a factor N smaller array through averaging over NxNxN cells into 1 cell through some linear interpolation or averaging approach – somada141 Apr 01 '13 at 20:50

2 Answers2

7

Here is an example of 3D interpolation on an irregular grid using scipy.interpolate.griddata.

import numpy as np
import scipy.interpolate as interpolate
import matplotlib.pyplot as plt


def func(x, y, z):
    return x ** 2 + y ** 2 + z ** 2

# Nx, Ny, Nz = 181, 181, 421
Nx, Ny, Nz = 18, 18, 42

subsample = 2
Mx, My, Mz = Nx // subsample, Ny // subsample, Nz // subsample

# Define irregularly spaced arrays
x = np.random.random(Nx)
y = np.random.random(Ny)
z = np.random.random(Nz)

# Compute the matrix D of shape (Nx, Ny, Nz).
# D could be experimental data, but here I'll define it using func
# D[i,j,k] is associated with location (x[i], y[j], z[k])
X_irregular, Y_irregular, Z_irregular = (
    x[:, None, None], y[None, :, None], z[None, None, :])
D = func(X_irregular, Y_irregular, Z_irregular)

# Create a uniformly spaced grid
xi = np.linspace(x.min(), x.max(), Mx)
yi = np.linspace(y.min(), y.max(), My)
zi = np.linspace(y.min(), y.max(), Mz)
X_uniform, Y_uniform, Z_uniform = (
    xi[:, None, None], yi[None, :, None], zi[None, None, :])

# To use griddata, I need 1D-arrays for x, y, z of length 
# len(D.ravel()) = Nx*Ny*Nz.
# To do this, I broadcast up my *_irregular arrays to each be 
# of shape (Nx, Ny, Nz)
# and then use ravel() to make them 1D-arrays
X_irregular, Y_irregular, Z_irregular = np.broadcast_arrays(
    X_irregular, Y_irregular, Z_irregular)
D_interpolated = interpolate.griddata(
    (X_irregular.ravel(), Y_irregular.ravel(), Z_irregular.ravel()),
    D.ravel(),
    (X_uniform, Y_uniform, Z_uniform),
    method='linear')

print(D_interpolated.shape)
# (90, 90, 210)

# Make plots
fig, ax = plt.subplots(2)

# Choose a z value in the uniform z-grid
# Let's take the middle value
zindex = Mz // 2
z_crosssection = zi[zindex]

# Plot a cross-section of the raw irregularly spaced data
X_irr, Y_irr = np.meshgrid(sorted(x), sorted(y))
# find the value in the irregular z-grid closest to z_crosssection
z_near_cross = z[(np.abs(z - z_crosssection)).argmin()]
ax[0].contourf(X_irr, Y_irr, func(X_irr, Y_irr, z_near_cross))
ax[0].scatter(X_irr, Y_irr, c='white', s=20)   
ax[0].set_title('Cross-section of irregular data')
ax[0].set_xlim(x.min(), x.max())
ax[0].set_ylim(y.min(), y.max())

# Plot a cross-section of the Interpolated uniformly spaced data
X_unif, Y_unif = np.meshgrid(xi, yi)
ax[1].contourf(X_unif, Y_unif, D_interpolated[:, :, zindex])
ax[1].scatter(X_unif, Y_unif, c='white', s=20)
ax[1].set_title('Cross-section of downsampled and interpolated data')
ax[1].set_xlim(x.min(), x.max())
ax[1].set_ylim(y.min(), y.max())

plt.show()

enter image description here

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Thanks a lot unutbu, that's exactly what I needed. You rock! – somada141 Apr 04 '13 at 14:56
  • Nice. Fwiw, `griddata` (qhull) in 3d looks at only 4 neighbors around each point interpolated -- rather few if the data is at all noisy (sigma / 2). – denis Jun 26 '13 at 10:51
  • @denis: Thanks for the info. Would you recommend something else for 3D data? – unutbu Jun 26 '13 at 10:58
  • Good question; mo interpolator covers all use cases. If griddata works, don't fix it. If the grid is rectangular with slowly-varying x y z, which I think is the OP's case, then take a look at (ahem) Intergrid under [interpolation-of-regularly-sampled-3d-data-with-different-intervals](http://stackoverflow.com/questions/16217995) – denis Jun 27 '13 at 16:37
  • @denis: I think I've managed to [apply Intergrid to the above problem](https://gist.github.com/anonymous/5879738). Would you mind taking a look to see if I used Intergrid properly? My method of setting up the array of points to pass to interfunc feels rather clunky. Am I doing it right or is there a better way? – unutbu Jun 27 '13 at 19:51
  • @unutbu: Thanks for that. Must admit that I avoid mgrid, ogrid, meshgrid ("Whenever something can be done in two ways, someone will be confused") so just keep 2d arrays of grid points, N x 3, uniform or nonuniform. A version of your code with such is under https://gist.github.com/denis-bz/5891891 . See the log: Griddata 121 sec, intergrid / map_coordinates .003 sec ? – denis Jun 29 '13 at 17:15
  • @unutbu, you're very welcome -- have learned a lot from you. (Repeat from [numpy-broadcast-array](http://stackoverflow.com/questions/3651099/numpy-broadcast-array) from 2010: what's a good place for short tutorial articles ?) – denis Jun 30 '13 at 14:50
  • @denis: I recently came across [IPython Notebook Viewer](http://nbviewer.ipython.org/). That might be a good place to share articles. – unutbu Jun 30 '13 at 15:05
1

In short: doing interpolation in each dimension separately is the right way to go.


You can simply average every 5x5x5 cube and return the results. However, if your data is supposed to be continuous, you should understand that is not good subsampling practice, as it will likely induce aliasing. (Also, you can't reasonably call it "interpolation"!)

Good resampling filters need to be wider than the resampling factor in order to avoid aliasing. Since you are downsampling, you should also realize that your resampling filter needs to be scaled according to the destination resolution, not the original resolution -- in order to interpolate properly, it will likely need to be 4 or 5 times as wide as your 5x5x5 cube. This is a lot of samples -- 20*20*20 is way more than 5*5*5...

So, the reason why practical implementations of resampling typically filter each dimension separately is that it is more efficient. By taking 3 passes, you can evaluate your filter using far fewer multiply/accumulate operations per output sample.

comingstorm
  • 25,557
  • 3
  • 43
  • 67
  • I think averaging gets a bad rap on aliasing. It's not perfect, but it's better than many alternatives. Anything that does away with aliasing entirely becomes unacceptably blurred or has too much ringing. – Mark Ransom Apr 01 '13 at 18:56
  • @comingstrom: Thanks for the response! So what you'd suggest is doing sth like interp1d on every dimension separately? What about interp2d on each x-y plane and then interp1d along every z line? Would that introduce artifacts or more interp errors than necessary? – somada141 Apr 01 '13 at 19:03
  • @somada141, you have to be very careful with the word "interpolation". It usually means selecting a value between 2 adjacent points, but that's not what you want to do here. Without knowing anything specifically about `interp1d` or `interp2d` I suspect they're using the definition I gave above. – Mark Ransom Apr 01 '13 at 20:30
  • @somada141, I have not used numpy/scipy myself, but I can google... have you looked at scipy.signal.resample? As Mark Ransom says, `interp1d` and `interp2d` appear to use splines to do actual interpolation, which is not actually what you want when resizing images. – comingstorm Apr 01 '13 at 22:03
  • Also, take a look at this [answer](http://stackoverflow.com/questions/13242382/resampling-a-numpy-array-representing-an-image)... – comingstorm Apr 01 '13 at 22:06