How to find the padding size of an image?

1.4k views Asked by At

I have a dicom image but the image is padded. I have code to remove the padding from the image so that only the scan is left but I have to open the image using ImageJ and manually find min and max values for the x and y axis for where the image starts and ends. The scan has a gray value range of -3000 to 2000. The padded area has a value of 0. Is there a way to find these min and max values without having do it manually?

Original Image:

enter image description here

Desired Image:

enter image description here

3

There are 3 answers

0
Dave Chen On BEST ANSWER

Below a Python script using SimpleITK that crops out the background.

The basic idea is that it creates a mask image of pixels that are not the background value. Then it uses SimpleITK's LabelShapeStatisticsImageFilter to find the bounding box for the non-zero pixels in that mask image.

import SimpleITK as sitk

img = sitk.ReadImage("padded-image.png")

# Grey background in this example
bg_value = 161

# Create a mask image that is just non-background pixels
fg_mask = (img != bg_value)

# Compute shape statistics on the mask
lsif = sitk.LabelShapeStatisticsImageFilter()
lsif.Execute(fg_mask)

# Get the bounds of the mask.
# Bounds are given as [Xstart, Ystart, Xwidth, Ywidth]
bounds = lsif.GetBoundingBox(1)
print(bounds)

Xmin_crop = bounds[0]
Ymin_crop = bounds[1]

Xmax_crop = img.GetWidth() - (bounds[0]+bounds[2])
Ymax_crop = img.GetHeight() - (bounds[1]+bounds[3])

# Crop parameters are how much to crop off each side
cropped_img = sitk.Crop(img, [Xmin_crop, Ymin_crop], [Xmax_crop, Ymax_crop])

sitk.Show(cropped_img)

sitk.WriteImage(cropped_img, "cropped-image.png")

Because I used your 8-bit PNG image, the background value is set to 161. If you use your original 16-bit DICOM CT, you'd use a background value of 0. SimpleITK can read DICOM, along with a number of other image formats.

For more info about the LabelShapeStatisticsImageFilter class, here's the documentation: https://simpleitk.org/doxygen/latest/html/classitk_1_1simple_1_1LabelShapeStatisticsImageFilter.html#details

1
Richard On

Without having to resort to something as complex (and large/slow to import) as SITK or CV with complicated image analysis - you can do it easily just using numpy.

That's going to be a lot faster and reliable IMHO:

# if a is your image:

same_cols = np.all(a == a[0, :], axis=0)
same_cols_index = np.where(same_cols==False)[0]
C0,C1 = same_cols_index[0], same_cols_index[-1] + 1

same_rows = np.all(a == a[:, 0], axis=1)
same_rows_index = np.where(same_rows==False)[0]
R0,R1 = same_rows_index[0], same_rows_index[-1] + 1

print('rows', R0, R1)
print('cols', C0, C1)

a_snipped = a[R0:R1, C0:C1]

The logic here is

  1. Find all rows and columns that have all values the same as the first row or column. You could replace that to be all rows/cols with value == 0 if you want
  2. Get the indexes of the rows/columns from (1) where they are not all the same (ie == False)
  3. Get the first and last index where they aren't all the same
  4. Use the row/column first and last indicies to get the corresponding slice of your array (note you need to add 1 to the last index to include it in the slice)

Example

# make a sample image

a = np.zeros((512,512), dtype=np.int32)
r0, r1 = 53, 421
c0, c1 = 43, 470
rnd = np.random.randint(-3000, 2000, (r1-r0, c1-c0))
a[r0:r1, c0:c1] = rnd
plt.imshow(a, cmap='gray', vmin=-50, vmax=50)

original

same_cols = np.all(a == a[0, :], axis=0)
same_cols_index = np.where(same_cols==False)[0]
C0,C1 = same_cols_index[0], same_cols_index[-1] + 1

same_rows = np.all(a == a[:, 0], axis=1)
same_rows_index = np.where(same_rows==False)[0]
R0,R1 = same_rows_index[0], same_rows_index[-1] + 1

print('rows', R0, R1)
print('cols', C0, C1)

a_snipped = a[R0:R1, C0:C1]

plt.imshow(a_snipped, cmap='gray', vmin=-3000, vmax=2000)

rows 53 421 cols 43 470

snipped

0
fmw42 On

Here is an alternate way in Python/OpenCV using color thresholding and contours to find the bounding box.

Input:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread('scan.png')

# threshold on gray color (161,161,161)
lower = (161,161,161)
upper = (161,161,161)
thresh = cv2.inRange(img, lower, upper)

# invert threshold image so border is black and center box is white
thresh = 255 - thresh

# get external contours (presumably just one) 
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
cntr = contours[0]
x,y,w,h = cv2.boundingRect(cntr)

# crop to bounding rectangle
crop = img[y:y+h, x:x+w]

# save cropped image
cv2.imwrite('scan_thresh.png',thresh)
cv2.imwrite('scan_crop.png',crop)

cv2.imshow("THRESH", thresh)
cv2.imshow("CROP", crop)
cv2.waitKey(0)
cv2.destroyAllWindows()

Thresholded image:

enter image description here

Cropped Result:

enter image description here