Search code examples
pythonimage-processinglookup-tablesbinary-image

Is there a Python equivalent to Matlab's makelut, applylut?


I am working with some image processing routines, using binary images. In Matlab I can create a lookup table which provides the output for every possible 2^9=512 configurations of 3 x 3 neighbourhoods. That is, I can write a function func which produces a 0 or 1 for such a neighbourhood, and then create a lookup table with

lut = makelut(func,3)

(the "3" indicating the size of neighbourhood). Then that lookup table can be applied to my binary image im with

applylut(im, lut)

But how can I do the same thing in Python? There is an example given here:

http://pydoc.net/Python/scikits-image/0.4.2/skimage.morphology.skeletonize/

which certainly works, but seems very complicated, at least compared to Matlab's commands.


Solution

  • The filters defined in scipy.ndimage may be of use to you. If none of the pre-defined filters match your intent, you can apply a custom filter using scipy.ndimage.generic_filter.

    For example, you can reproduce the result shown on the Mathworks applylut doc page with:

    import numpy as np
    import scipy.ndimage as ndimage
    from PIL import Image
    
    filename = '/tmp/PerformErosionUsingA2by2NeighborhoodExample_01.png'
    img = Image.open(filename).convert('L')
    arr = np.array(img)
    def func(x):
        return (x==255).all()*255
    arr2 = ndimage.generic_filter(arr, func, size=(2,2))
    new_img = Image.fromarray(arr2.astype('uint8'), 'L')
    new_img.save('/tmp/out.png')
    

    PerformErosionUsingA2by2NeighborhoodExample_01.png:

    enter image description here

    out.png:

    enter image description here

    Note that in this case, ndimage.grey_erosion can produce the same result, and since it is not calling a Python function once for every pixel, it's also a lot faster:

    arr3 = ndimage.grey_erosion(arr, size=(2,2))
    print(np.allclose(arr2,arr3))
    # True
    

    Depending on the kind of computation you wish to perform in func, another faster alternative may be to express the result as a NumPy computation on slices. For example, the above grey_erosion could also be expressed as

    arr4 = np.pad(arr.astype(bool), ((1,0),(1,0)), 'reflect')
    arr4 = arr4[:-1,:-1] & arr4[1:,:-1] & arr4[:-1,1:] & arr4[1:,1:]
    arr4 = arr4.astype('uint8')*255
    assert np.allclose(arr3, arr4)
    

    Again this is much faster than using generic_filter since here the computation is being performed on whole arrays rather than pixel-by-pixel.