Search code examples
python-3.ximageimage-processingscikit-imagendimage

Isolating the head in a grayscale CT image using Python


I am dealing with CT images that contain the head of the patient but also 'shadows' of the metalic cylinder.

enter image description here

These 'shadows' can appear down, left or right. In the image above it appears only on the lower side of the image. In the image below it appears in the left and the right directions. I don't have any prior knowledge of whether there is a shadow of the cylinder in the image. I must somehow detect it and remove it. Then I can proceed to segment out the skull/head.

enter image description here

To create a reproducible example I would like to provide the numpy array (128x128) representing the image but I don't know how to upload it to stackoverflow.

How can I achieve my objective?

I tried segmentation with ndimage and scikit-image but it does not work. I am getting too many segments.


enter image description here


12 Original Images

enter image description here

The 12 Images Binarized

enter image description here

The 12 Images Stripped (with dilation, erosion = 0.1, 0.1)

enter image description here

The images marked with red color can not help create a rectangular mask that will envelop the skull, which is my ultimate objective.

Please note that I will not be able to inspect the images one by one during the application of the algorithm.


Solution

  • You could use a combination of erosion (with an appropriate number of iterations) to remove the thin details, followed by dilation (also with an appropriate number of iterations) to restore the non-thin details to approximately the original size.

    In code, this would look like:

    import io
    import requests
    
    import numpy as np
    import scipy as sp
    import matplotlib as mpl
    import PIL as pil
    
    import scipy.ndimage
    import matplotlib.pyplot as plt
    
    
    # : load the data
    url = 'https://i.sstatic.net/G4cQO.png'
    response = requests.get(url)
    img = pil.Image.open(io.BytesIO(response.content)).convert('L')
    arr = np.array(img)
    mask_arr = arr.astype(bool)
    
    # : strip thin objects
    struct = None
    n_erosion = 6
    n_dilation = 7
    strip_arr = sp.ndimage.binary_dilation(
        sp.ndimage.binary_erosion(mask_arr, struct, n_erosion),
        struct, n_dilation)
    
    plt.imshow(mask_arr, cmap='gray')
    plt.imshow(strip_arr, cmap='gray')
    plt.imshow(mask_arr ^ strip_arr, cmap='gray')
    

    Starting from this image (mask_arr):

    mask_arr

    One would get to this image (strip_arr):

    strip_arr

    The difference being (mask_arr ^ strip_arr):

    xor_arr


    EDIT

    (addressing the issues raised in the comments)

    Using a different input image, for example a binarization of the input with a much lower threshold will help having larger and non-thin details of the head that will not disappear during erosion.

    Alternatively, you may get more robust results by fitting an ellipse to the head.