Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: downsample 2D numpy array by a non-integer factor

I need to downsample a 2D numpy array by a non-integer factor (e.g. 100x100 array to a 45x45 array) in a way that performs local averaging, just like Photoshop/gimp would do that for an image. I need double precision. Current options can't do it well.

  • scipy.ndimage.zoom does not perform averaging, and basically uses nearest-neighbor sampling (see previous question scipy.ndimage.interpolation.zoom uses nearest-neighbor-like algorithm for scaling-down )

  • scipy.misc.imresize converts an array to an int8; I need more precision and floating point

  • skimage.transform.rescale also uses nearest-neighbor and forwards you to skimage.transform.downscale_local_mean for local averaging,

  • skimage.transform.downscale_local_mean can only perform integer scaling factor (and pads image with zeros if the factor is non-integer). Integer scaling factor is a trivial numpy excersice.

Did I miss any other options?

like image 716
Maxim Imakaev Avatar asked Dec 06 '15 20:12

Maxim Imakaev


1 Answers

I ended up writing a small function that upscales the image using scipy.ndimage.zoom, but for downscaling it first upscales it to be the multiple of the original shape, and then downscales by block-averaging. It accepts any other keyword arguments for scipy.zoom (order and prefilter)

I'm still looking for a cleaner solution using available packages.

def zoomArray(inArray, finalShape, sameSum=False, **zoomKwargs):
    inArray = np.asarray(inArray, dtype = np.double)
    inShape = inArray.shape
    assert len(inShape) == len(finalShape)
    mults = []
    for i in range(len(inShape)):
        if finalShape[i] < inShape[i]:
            mults.append(int(np.ceil(inShape[i]/finalShape[i])))
        else:
            mults.append(1)
    tempShape = tuple([i * j for i,j in zip(finalShape, mults)])

    zoomMultipliers = np.array(tempShape) / np.array(inShape) + 0.0000001
    rescaled = zoom(inArray, zoomMultipliers, **zoomKwargs)

    for ind, mult in enumerate(mults):
        if mult != 1:
            sh = list(rescaled.shape)
            assert sh[ind] % mult == 0
            newshape = sh[:ind] + [sh[ind] / mult, mult] + sh[ind+1:]
            rescaled.shape = newshape
            rescaled = np.mean(rescaled, axis = ind+1)
    assert rescaled.shape == finalShape

    if sameSum:
        extraSize = np.prod(finalShape) / np.prod(inShape)
        rescaled /= extraSize
    return rescaled
like image 53
Maxim Imakaev Avatar answered Oct 20 '22 01:10

Maxim Imakaev