Search code examples
pythonnumpy3dresizescikit-image

How to resize an image in python using skimage?


I am working with 3D CT images and trying to resize the binary segmentation mask from (564,359,359) into (128,128,128) as follows:

from skimage.transform import resize
mask_resized= resize(binary_mask, (128, 128, 128), order=0)

Binary mask before resizing looks as follows:

enter image description here

The resulting output is not binary (yields a series of values between 0 & 1) and the output is distorted as follows:

enter image description here

I tried image_resized_seg = np.rint(image_resized_seg), but this yields full black images for some slices containing the segmentation mask.

I tried the following as well, which also gives distorted images and some slices containing mask is missing in the output:

from scipy import ndimage

def resize_volume_mask(img):
    """Resize across z-axis"""
    # Set the desired depth
    desired_depth = 128 
    desired_width = 128 
    desired_height = 128 
    # Get current depth
    current_depth = img.shape[0] #-1
    current_width = img.shape[1] #0
    current_height = img.shape[2] #1
    # Compute depth factor
    depth = current_depth / desired_depth
    width = current_width / desired_width
    height = current_height / desired_height
    depth_factor = 1 / depth
    width_factor = 1 / width
    height_factor = 1 / height
    # Rotate
    #img = ndimage.rotate(img, 90, reshape=False)
    # Resize across z-axis
    img = ndimage.zoom(img, (depth_factor, width_factor, height_factor), order=0)
    return img

Could someone please advise on how to resize the segmentation mask without loss of information, while keeping it binary?


Solution

  • The resulting output is not binary

    This is normal. When an image is resized, an interpolation is performed. This is especially required when the target image is smaller than the source because the amount of information in the target image is smaller. When the target image is bigger, the interpolation scheme enable to get something that looks "nice" for us.

    There are many interpolation method. The most basic one is nearest. It does not really interpolate the pixels. Instead, it find the nearest one in the source image matching with the location of the target image. Typical interpolation methods are bilinear and cubic interpolation (using basic polynomials). By default, skimage.transform.resize uses a Gaussian filter for a downsampling since anti_aliasing is not set and the input datatype is not bool:

    Whether to apply a Gaussian filter to smooth the image prior to downsampling. It is crucial to filter when downsampling the image to avoid aliasing artifacts. If not specified, it is set to True when downsampling an image whose data type is not bool.

    Thus you need to use a boolean mask, not a floating-point one, or to set anti_aliasing to false.

    this yields full black images for some slices containing the segmentation mask.

    This is certainly because the floating-point value in the image have a range between 0 and 1 (this is the standard for images defined as an array of floating-point values). np.rint will result in 0-1 integers that skimage will interpret as standard integer components of pixels defined in the 0-255 range. As a result, the whole image seems black but it is not completely black. You should multiply the result by 255 so to see the 1 value.

    which also gives distorted images
    resize the segmentation mask without loss of information

    A small distortion is normal. It is due to the loss of information. Nearly all downscale will cause a loss of information (it is not the case only if the source image is an upscaled version of the downscaled target image).

    Using a threshold after the downscale gives the following image:

    enter image description here

    It looks quite good compared to the initial image and the fact that skimage use a Gaussian filter before. Using anti_aliasing=false certainly give a better result.


    To summary, the best solution is certainly simply to call resize(binary_mask, (128, 128, 128), anti_aliasing=false, order=0) or convert the mask first using resize(binary_mask.astype(bool), (128, 128, 128), order=0). You may need to convert the result back to floating point regarding the next operations in your code. If so, be careful to the range of the value.