Reputation: 412
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:
Upvotes: 0
Views: 425
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
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
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
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:
Of course getting the 'outer' boundary is just the difference of the dilated image and the original image.
Upvotes: 0