numpyscipyscipy.ndimage

Shaped gradient fill in numpy/scipy


Looking for a way to fill all of the values within an arbitrary shape with a gradient of values... which must follow the outline of the shape. For example, the "shaped gradient" fill tool in gimp would give you:

enter image description here

Output should be a 2d numpy array.


Solution

  • You could take a look at scipy.ndimage.morphology.distance_transform_edt. This will return the distance to the closest background pixel.

    First, you will need to create a binary image of your arbitrary shape

    import numpy as np
    from scipy.ndimage.morphology import distance_transform_edt
    
    # create dummy image
    a = np.arange(100).reshape([10, 10])
    
    # use threshold to define arbitrary shape
    b = (a > 54).astype('uint8')
    print(b)
    
    [[0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 1 1 1 1 1]
     [1 1 1 1 1 1 1 1 1 1]
     [1 1 1 1 1 1 1 1 1 1]
     [1 1 1 1 1 1 1 1 1 1]
     [1 1 1 1 1 1 1 1 1 1]]
    

    Then, apply the distance transform to the binary image. The output will look like below, with smaller values corresponding to those closer to the edge of the binary object.

    # apply Euclidean distance transform
    d = distance_transform_edt(b)
    print(d.round(2))
    
    [[0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  ]
     [0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  ]
     [0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  ]
     [0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  ]
     [0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  ]
     [0.   0.   0.   0.   0.   1.   1.   1.   1.   1.  ]
     [1.   1.   1.   1.   1.   1.41 2.   2.   2.   2.  ]
     [2.   2.   2.   2.   2.   2.24 2.83 3.   3.   3.  ]
     [3.   3.   3.   3.   3.   3.16 3.61 4.   4.   4.  ]
     [4.   4.   4.   4.   4.   4.12 4.47 5.   5.   5.  ]]
    

    A color map could then be defined for the range of values in d.