RobMcC
RobMcC

Reputation: 412

Add a border to a irregular image in python

I would like to add a border to an irregular 3 dimensional image that is stored as a binary numpy array. I thought of perhaps shifting the image a voxel left/right, forward/back, up/down and then combining those 6 images and subtracting the original image; using the below code:

#make copy of the image shifted one voxel to the left
image_border = np.zeros((img_dim[0], img_dim[1], img_dim[2]))    
for x in xvoxels:
    x_slice_original = image[x, :, :]
    x_slice_new = np.zeros((img_dim[1], img_dim[2]))
    for y in yvoxels:
        for z in zvoxels:
            if x_slice_original[y, z] == 1:
                x_slice_new[(y-1), z] = 1
            else:
                x_slice_new[(y-1), z] = 0
    image_border[x, :, :] = x_slice_new

That seems a bit inefficient though and was wondering if anyone had any neater solutions?

Edit: The image is an MRI image 91*109*91 voxels. xvoxels and zvoxels are lists 0:90, yvoxels is a list 0:108.A 2D slice of the image in question is below:enter image description here

Upvotes: 0

Views: 425

Answers (2)

Paul Panzer
Paul Panzer

Reputation: 53029

Here is a very simple method using scipy.ndimage.binary_dilation which is essentially a library version of the operation you have in mind. What's left to do for us is blank out the original voxels. As you can see the whole thing fits comfortably in one line.

import numpy as np
from scipy import ndimage

# create mock data
i,j,k = np.ogrid[:10,:10,:10]
ball = ((i-5)**2+(j-5)**2+(k-5)**2 < 16).view(np.int8)

# create border
shell = (ndimage.binary_dilation(ball) & ~ball).view(np.int8)

# that's all folks, show one section of original and border
print(ball[5])
# [[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 0 0]
#  [0 0 1 1 1 1 1 1 1 0]
#  [0 0 1 1 1 1 1 1 1 0]
#  [0 0 1 1 1 1 1 1 1 0]
#  [0 0 1 1 1 1 1 1 1 0]
#  [0 0 1 1 1 1 1 1 1 0]
#  [0 0 0 1 1 1 1 1 0 0]
#  [0 0 0 0 0 0 0 0 0 0]]
print(shell[5])
# [[0 0 0 0 0 0 0 0 0 0]
#  [0 0 0 1 1 1 1 1 0 0]
#  [0 0 1 0 0 0 0 0 1 0]
#  [0 1 0 0 0 0 0 0 0 1]
#  [0 1 0 0 0 0 0 0 0 1]
#  [0 1 0 0 0 0 0 0 0 1]
#  [0 1 0 0 0 0 0 0 0 1]
#  [0 1 0 0 0 0 0 0 0 1]
#  [0 0 1 0 0 0 0 0 1 0]
#  [0 0 0 1 1 1 1 1 0 0]]

Upvotes: 0

Tom de Geus
Tom de Geus

Reputation: 5965

What you want to do is a process called 'dilation'. The idea, for a binary image, is that you loop over all pixels in your image. If the pixel is 'True' you make a specified set of pixels around it also 'True' (which is defined by the 'kernel' or 'structure'). For example

0,0,0,0,0
0,0,0,0,0
0,0,1,1,0
0,0,0,0,0
0,0,0,0,0

with a kernel

0,1,0
1,1,1
0,1,0
  1. When reaching (2,2) it becomes:

    0,0,0,0,0
    0,0,1,0,0
    0,1,1,1,0
    0,0,1,0,0
    0,0,0,0,0
    
  2. When reaching (2,3) it becomes:

    0,0,0,0,0
    0,0,1,1,0
    0,1,1,1,1
    0,0,1,1,0
    0,0,0,0,0
    

You can repeat this, or use a different kernel. For example:

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

im = np.zeros((90,90),dtype='bool')
im[30:61,30:61] = True

fig = plt.figure(figsize=(18,6))
fig.set_tight_layout(True)

fig.add_subplot(1,3,1)
plt.imshow(im,interpolation='nearest')

fig.add_subplot(1,3,2)
plt.imshow(scipy.ndimage.binary_dilation(im,iterations=4),interpolation='nearest')

fig.add_subplot(1,3,3)
plt.imshow(scipy.ndimage.binary_dilation(im,iterations=4,structure=np.ones((3,3))),interpolation='nearest')

plt.show()

The result:

enter image description here

Of course getting the 'outer' boundary is just the difference of the dilated image and the original image.

Upvotes: 0

Related Questions