Module pymskt.image.cartilage_processing

Expand source code
import SimpleITK as sitk
import numpy as np
from scipy import ndimage as ndi


def CofM(array):
    '''
    Get center of mass for a row of a binary 2D image. 
    Parameters
    ----------
    array : 1D array
        Individual row of a 2D image.  
    Returns
    -------
    centerPixels : 
        Average location of 1s in the row
    Notes
    -----
    Calculates the average location of cartilage for the row of image being analyzed. 
    Returns 0 if there are no pixels
    
    '''
    pixels = np.where(array==1)
    centerPixels = np.mean(pixels)
    nans = np.isnan(centerPixels)
    if nans == True:
        centerPixels = 0
    return(centerPixels)

def get_y_CofM(flattenedSeg):
    '''
    Get CofM of femoral cartilage for each row of the flattened segmentation. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    yCofM : 
        Find the CofM for each row of the image. 
    Notes
    -----
    Get the x/y coordinates for the CofM for each row of the flattened segmentation. 
    
    '''
    locationFemur = np.where(flattenedSeg==1)
    yCofM = np.zeros((flattenedSeg.shape[0], 2), dtype=int)
    
    # only calculate for rows with cartilage. 
    minRow = np.min(locationFemur[0])
    maxRow = np.max(locationFemur[0])
    
    # iterate over rows of image, get CofM, store CofM for row. 
    for x in range(minRow, maxRow):
        yCofM[x, 0] = x #store the x-coordinate (row) we calcualted CofM for. 
        yCofM[x, 1] = int(CofM(flattenedSeg[x, :])) # store the CofM value (make it an integer for indexing)
    yCofM = yCofM[minRow+10:maxRow-10,:] # remove 10 most medial and most lateral pixels of femoral cartilage. 
    return(yCofM) 

def absolute_CofM(flattenedSeg):
    '''
    Get absolute CofM of all the femoral cartilage pixels
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    centerX : 
        The CofM in the X direction for the segmentation  
    centerY : 
        The CofM in the Y direction for the segmentation
    Notes
    -----
    Get the x/y coordinates for the CofM for the whole flattened segmentation
    
    '''
    femurPoints = np.where(flattenedSeg==1)
    centerX = np.mean(femurPoints[0])
    centerY = np.mean(femurPoints[1])
    return(centerX, centerY)

def findNotch(flattenedSeg, trochleaPositionX=1000):
    '''
    Get the X Y position of the trochlear notch - where medial/lateral sides of the femur meet. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    trochleaPositionY : 
        Y position of trochlear notch  
    trochleaPositionX : 
        X position of trochlear notch
    Notes
    -----
    Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the
    femoral cartilage.
    
    '''
    # Goal is to find the most anterior point that is between the medial/lateral condyles

    # First guess at the troch notch in the 1st axis (med/lat axis) is the location with the smallest value for
    # the 2nd axis CofM. This is because in axis 1, negative is anterior and we expect the most anterior CofM should 
    # roughly align with the trochlear notch.
    y_CofM = get_y_CofM(flattenedSeg)
    first_guess = y_CofM[np.argmin(y_CofM[:,1]), 0]
    # the second guess is just the CofM of the whole cartilage. 
    centerX, centerY = absolute_CofM(flattenedSeg)
    second_guess = centerX

    # We use the 2 guesses to help define a search space for the trochlear notch.
    min_search = int(np.min((first_guess,second_guess))-20)
    max_search = int(np.max((first_guess,second_guess))+20)

    # now, we iterate over all of the rows (axis 1) of the search space (moving in the medial/lateral direction)
    # we are looking for the row where the most posterior point (back of femur) is furthest anterior (notch). 
    for y in range(min_search, max_search):
        # At each row, we find most posterior pixel labeled as cartilage. 
        try:
            trochleaPosition_test = np.max(np.where(flattenedSeg[y,:]==1))
        except ValueError:
            # if there is no cartilage we'll get a ValueError exception. 
            # in that case, set this value to be the max it can be (the size of the first axis)
            trochleaPosition_test = flattenedSeg.shape[1]
        # if the most posterior point for this row is more anterior than the current trochleaPositionX,
        # then update this to be the new trochlear notch.
        if trochleaPosition_test < trochleaPositionX:
            trochleaPositionX = trochleaPosition_test
            trochleaPositionY = y

    return(trochleaPositionY, trochleaPositionX+1)

def getAnteriorOfWeightBearing(segArray, femurIndex=1):
    '''
    Prepare full segmentation and extract the trochlear notch location. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    femurIndex : int
        Index of the label used to localize the femur in the array. 
    Returns
    -------
    trochleaPositionY : 
        Y position of trochlear notch  
    trochleaPositionX : 
        X position of trochlear notch
    Notes
    -----
    Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the femoral cartilage. 
    First flatten and fill any holes in the segmentation. 
    
    '''

    femurSegmentation = np.zeros_like(segArray)
    femurSegmentation[segArray == femurIndex] = 1
    flattenedSegmentation = np.amax(femurSegmentation, axis=1)
    flattened_seg_filled = ndi.binary_fill_holes(flattenedSegmentation)
    trochY, trochX = findNotch(flattened_seg_filled)
    return(trochY, trochX)
    
def getCartilageSubRegions(segArray, anteriorWBslice, posteriorWBslice, trochY,
                           femurLabel=1, medTibiaLabel=2, latTibiaLabel=3, antFemurMask=5, 
                           medWbFemurMask=6, latWbFemurMask=7, medPostFemurMask=8, latPostFemurMask=9):
    '''
    Take cartilage segmentation, and decompose femoral cartilage into subregions of interest.  
    Parameters
    ----------
    segArray : array
        3D array with segmentation for the cartialge regions. 
    anteriorWBslice : int
        Slice that seperates the anterior and weight bearing femoral cartilage.  
    posteriorWBslice : int
        Slice that seperates the weight bearing and posterior femoral cartilage. 
    trochY : int
        Slice that differentiates medial / lateral femur - trochlear notch Y component. 
    femurLabel : int
        Label that femur is in the segArray
    medTibiaLabel : int
        Label that medial tibia is in the segArray
    latTibiaLabel : int
        Label that lateral tibia is in the segArray
    antFemurMask : int
        Label anterior femur should be labeled in final segmentation. 
    medWbFemurMask : int
        Label medial weight bearing femur should be labeled in final segmentation.
    latWbFemurMask : int
        Label lateral weight bearing femur should be labeled in final segmentation. 
    medPostFemurMask : int
        Label medial posterior femur should be labeled in final segmentation. 
    latPostFemurMask : int
        Label lateral posterior femur should be labeled in final segmentation.
    Returns
    -------
    final_segmentation : array
        3D array with the updated segmentations - including weightbearing, medial/latera, anterior, and posterior. 
    Notes
    -----
    
    '''
    
    #array to store final segmentation
    final_segmentation = np.zeros_like(segArray)
    
    #create masks for ant/wb/posterior femur
    anterior_femur_mask = np.zeros_like(segArray)
    anterior_femur_mask[:,:,:anteriorWBslice] = 1

    wb_femur_mask = np.zeros_like(segArray)
    wb_femur_mask[:,:,anteriorWBslice:posteriorWBslice] = 1

    posterior_femur_mask = np.zeros_like(segArray)
    posterior_femur_mask[:,:,posteriorWBslice:] = 1
    
    #create seg of just femur - and then break it into the sub-regions
    femurSegArray = np.zeros_like(segArray)
    femurSegArray[segArray==femurLabel] = 1
    
    #find the center of the medial/lateral tibia - use to distinguish M/L femur ROIs
    locationMedialTibia = np.asarray(np.where(segArray==medTibiaLabel))
    locationLateralTibia = np.asarray(np.where(segArray==latTibiaLabel))
    
    centerMedialTibia = locationMedialTibia.mean(axis=1)
    centerLateralTibia = locationLateralTibia.mean(axis=1)

    med_femur_mask = np.zeros_like(segArray)
    lat_femur_mask = np.zeros_like(segArray)
    if centerMedialTibia[0] > trochY:
        med_femur_mask[trochY:,:,:] = 1
        lat_femur_mask[:trochY,:,:] = 1
    else:
        med_femur_mask[:trochY,:,:] = 1
        lat_femur_mask[trochY:,:,:] = 1

    final_segmentation[segArray!=femurLabel] = segArray[segArray!=femurLabel] 
    final_segmentation += (femurSegArray * anterior_femur_mask) * antFemurMask
    final_segmentation += (femurSegArray * wb_femur_mask * med_femur_mask) * medWbFemurMask
    final_segmentation += (femurSegArray * wb_femur_mask * lat_femur_mask) * latWbFemurMask
    final_segmentation += (femurSegArray * posterior_femur_mask * med_femur_mask) * medPostFemurMask
    final_segmentation += (femurSegArray * posterior_femur_mask * lat_femur_mask) * latPostFemurMask
    
    return(final_segmentation)

def verify_and_correct_med_lat_tib_cart(
    seg_array,  #sitk.GetArrayViewFromImage(seg)
    tib_label=6,
    med_tib_cart_label=2, 
    lat_tib_cart_label=3,
    ml_axis=0
):
    '''
    Verify that the medial and lateral tibial cartilage are correctly labeled.
    Parameters
    ----------
    seg_array : array
        3D array with segmentation for the cartilage/bone regions.
    tib_label : int
        Label that tibial cartilage is in the seg_array
    med_tib_cart_label : int
        Label that medial tibial cartilage is in the seg_array
    lat_tib_cart_label : int
        Label that lateral tibial cartilage is in the seg_array
    ml_axis : int
        Medial/lateral axis of the acquired knee MRI.
    
    Returns
    -------
    seg_array : array
        3D array with segmentation for the cartilage/bone regions.
        The tibial cartilage regions will have been updated to ensure
        all tib cart on med/lat sides are correctly classified.
        
    '''
    #get binary array for tibia
    array_tib = np.zeros_like(seg_array)
    array_tib[seg_array == tib_label] = 1
    #get binary array for tib cart
    array_tib_cart = np.zeros_like(seg_array)
    array_tib_cart[(seg_array == lat_tib_cart_label) + (seg_array == med_tib_cart_label)] = 1

    #get the locatons of med/lat cartilage & get their centroids
    med_cart_locs = np.asarray(np.where(seg_array == med_tib_cart_label))
    lat_cart_locs = np.asarray(np.where(seg_array == lat_tib_cart_label))
    middle_med_cart = med_cart_locs[ml_axis,:].mean()
    middle_lat_cart = lat_cart_locs[ml_axis,:].mean()

    #get location of tibia to get centroid of tibial plateau
    tib_locs = np.asarray(np.where(seg_array == tib_label))
    middle_tib = tib_locs[ml_axis, :].mean()
    center_tibia_slice = int(middle_tib)

    # infer the direction(s) for medial/lateral
    med_direction = np.sign(middle_med_cart - middle_tib)
    lat_direction = np.sign(middle_lat_cart - middle_tib)
    if med_direction == lat_direction:
        raise Exception('Middle of med and lat tibial cartilage on same side of centerline!')

    #create med/lat cartilage masks - binary for updating seg masks
    med_tib_cart_mask = np.zeros_like(seg_array)
    lat_tib_cart_mask = np.zeros_like(seg_array)

    if med_direction > 0:
        med_tib_cart_mask[center_tibia_slice:,...] = 1
        lat_tib_cart_mask[:center_tibia_slice,...] = 1
    elif med_direction < 0:
        med_tib_cart_mask[:center_tibia_slice,...] = 1
        lat_tib_cart_mask[center_tibia_slice:,...] = 1

    # create new med/lat cartilage arrays 
    new_med_cart_array = array_tib_cart * med_tib_cart_mask
    new_lat_cart_array = array_tib_cart * lat_tib_cart_mask

    #make copy of original segmentation array & update
    # med/lat tibial cartilage labels
    new_seg_array = seg_array.copy()
    new_seg_array[new_med_cart_array == 1] = med_tib_cart_label
    new_seg_array[new_lat_cart_array == 1] = lat_tib_cart_label
    
    return new_seg_array

def get_knee_segmentation_with_femur_subregions(seg_image,
                                                fem_cart_label_idx=1,
                                                wb_region_percent_dist=0.6,
                                                # femur_label=1,
                                                med_tibia_label=2,
                                                lat_tibia_label=3,
                                                ant_femur_mask=11,
                                                med_wb_femur_mask=12,
                                                lat_wb_femur_mask=13,
                                                med_post_femur_mask=14,
                                                lat_post_femur_mask=15,
                                                verify_med_lat_tib_cart=True,
                                                tibia_label=6,
                                                ml_axis=0
                                                ):
    """
    Give seg image of knee. Return seg image with all sub-regions of femur included. 

    Parameters
    ----------
    seg_image : SimpleITK.Image
        SimpleITK image of the segmentation to be processed. 
    fem_cart_label_idx : int, optional
        Label of femoral cartilage, by default 1
    wb_region_percent_dist : float, optional
        How large weightbearing region is (from not to posterior of condyles), by default 0.6
    femur_label : int, optional
        Seg label for the femur cartilage, by default 1
    med_tibia_label : int, optional
        Seg label for the medial tibia cartilage, by default 2
    lat_tibia_label : int, optional
        Seg label for the lateral tibia cartilage, by default 3
    ant_femur_mask : int, optional
        Seg label for the anterior femur region, by default 11
    med_wb_femur_mask : int, optional
        Seg label for medial weight-bearing femur, by default 12
    lat_wb_femur_mask : int, optional
        Seg label for lateral weight-bearing femur, by default 13
    med_post_femur_mask : int, optional
        Seg label for medial posterior femur, by default 14
    lat_post_femur_mask : int, optional
        Seg label for lateral posterior femur, by default 15
    verify_med_lat_tib_cart : bool, optional
        Whether to verify that medial and lateral tibial cartilage is on same side of centerline, by default True
    tibia_label : int, optional
        Seg label for the tibia, by default 6
    ml_axis : int, optional
        Medial/lateral axis of the acquired knee MRI, by default 0

    Returns
    -------
    SimpleITK.Image
        Image of the new/updated segmentation
    """
    troch_notch_y, troch_notch_x = getAnteriorOfWeightBearing(sitk.GetArrayViewFromImage(seg_image),
                                                              femurIndex=fem_cart_label_idx)
    loc_fem_z, loc_fem_y, loc_fem_x = np.where(sitk.GetArrayViewFromImage(seg_image) == fem_cart_label_idx)
    post_femur_slice = np.max(loc_fem_x)
    posterior_wb_slice = np.round((post_femur_slice - troch_notch_x) * wb_region_percent_dist + troch_notch_x).astype(int)
    new_seg_array = getCartilageSubRegions(sitk.GetArrayViewFromImage(seg_image),
                                           anteriorWBslice=troch_notch_x,
                                           posteriorWBslice=posterior_wb_slice,
                                           trochY=troch_notch_y,
                                           femurLabel=fem_cart_label_idx,
                                           medTibiaLabel=med_tibia_label,
                                           latTibiaLabel=lat_tibia_label,
                                           antFemurMask=ant_femur_mask,
                                           medWbFemurMask=med_wb_femur_mask,
                                           latWbFemurMask=lat_wb_femur_mask,
                                           medPostFemurMask=med_post_femur_mask,
                                           latPostFemurMask=lat_post_femur_mask
                                           )

    if verify_med_lat_tib_cart:
        new_seg_array = verify_and_correct_med_lat_tib_cart(new_seg_array,
                                                            tib_label=tibia_label,
                                                            med_tib_cart_label=med_tibia_label, 
                                                            lat_tib_cart_label=lat_tibia_label,
                                                            ml_axis=ml_axis) 
    seg_label_image = sitk.GetImageFromArray(new_seg_array)
    seg_label_image.CopyInformation(seg_image)
    return seg_label_image

Functions

def CofM(array)

Get center of mass for a row of a binary 2D image. Parameters


array : 1D array
Individual row of a 2D image.

Returns

centerPixels
Average location of 1s in the row

Notes

Calculates the average location of cartilage for the row of image being analyzed. Returns 0 if there are no pixels

Expand source code
def CofM(array):
    '''
    Get center of mass for a row of a binary 2D image. 
    Parameters
    ----------
    array : 1D array
        Individual row of a 2D image.  
    Returns
    -------
    centerPixels : 
        Average location of 1s in the row
    Notes
    -----
    Calculates the average location of cartilage for the row of image being analyzed. 
    Returns 0 if there are no pixels
    
    '''
    pixels = np.where(array==1)
    centerPixels = np.mean(pixels)
    nans = np.isnan(centerPixels)
    if nans == True:
        centerPixels = 0
    return(centerPixels)
def absolute_CofM(flattenedSeg)

Get absolute CofM of all the femoral cartilage pixels Parameters


flattenedSeg : 2D array
Axial flattened, and filled in femoral cartilage segmentation.

Returns

centerX
The CofM in the X direction for the segmentation
centerY
The CofM in the Y direction for the segmentation

Notes

Get the x/y coordinates for the CofM for the whole flattened segmentation

Expand source code
def absolute_CofM(flattenedSeg):
    '''
    Get absolute CofM of all the femoral cartilage pixels
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    centerX : 
        The CofM in the X direction for the segmentation  
    centerY : 
        The CofM in the Y direction for the segmentation
    Notes
    -----
    Get the x/y coordinates for the CofM for the whole flattened segmentation
    
    '''
    femurPoints = np.where(flattenedSeg==1)
    centerX = np.mean(femurPoints[0])
    centerY = np.mean(femurPoints[1])
    return(centerX, centerY)
def findNotch(flattenedSeg, trochleaPositionX=1000)

Get the X Y position of the trochlear notch - where medial/lateral sides of the femur meet. Parameters


flattenedSeg : 2D array
Axial flattened, and filled in femoral cartilage segmentation.

Returns

trochleaPositionY
Y position of trochlear notch
trochleaPositionX
X position of trochlear notch

Notes

Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the femoral cartilage.

Expand source code
def findNotch(flattenedSeg, trochleaPositionX=1000):
    '''
    Get the X Y position of the trochlear notch - where medial/lateral sides of the femur meet. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    trochleaPositionY : 
        Y position of trochlear notch  
    trochleaPositionX : 
        X position of trochlear notch
    Notes
    -----
    Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the
    femoral cartilage.
    
    '''
    # Goal is to find the most anterior point that is between the medial/lateral condyles

    # First guess at the troch notch in the 1st axis (med/lat axis) is the location with the smallest value for
    # the 2nd axis CofM. This is because in axis 1, negative is anterior and we expect the most anterior CofM should 
    # roughly align with the trochlear notch.
    y_CofM = get_y_CofM(flattenedSeg)
    first_guess = y_CofM[np.argmin(y_CofM[:,1]), 0]
    # the second guess is just the CofM of the whole cartilage. 
    centerX, centerY = absolute_CofM(flattenedSeg)
    second_guess = centerX

    # We use the 2 guesses to help define a search space for the trochlear notch.
    min_search = int(np.min((first_guess,second_guess))-20)
    max_search = int(np.max((first_guess,second_guess))+20)

    # now, we iterate over all of the rows (axis 1) of the search space (moving in the medial/lateral direction)
    # we are looking for the row where the most posterior point (back of femur) is furthest anterior (notch). 
    for y in range(min_search, max_search):
        # At each row, we find most posterior pixel labeled as cartilage. 
        try:
            trochleaPosition_test = np.max(np.where(flattenedSeg[y,:]==1))
        except ValueError:
            # if there is no cartilage we'll get a ValueError exception. 
            # in that case, set this value to be the max it can be (the size of the first axis)
            trochleaPosition_test = flattenedSeg.shape[1]
        # if the most posterior point for this row is more anterior than the current trochleaPositionX,
        # then update this to be the new trochlear notch.
        if trochleaPosition_test < trochleaPositionX:
            trochleaPositionX = trochleaPosition_test
            trochleaPositionY = y

    return(trochleaPositionY, trochleaPositionX+1)
def getAnteriorOfWeightBearing(segArray, femurIndex=1)

Prepare full segmentation and extract the trochlear notch location. Parameters


flattenedSeg : 2D array
Axial flattened, and filled in femoral cartilage segmentation.
femurIndex : int
Index of the label used to localize the femur in the array.

Returns

trochleaPositionY
Y position of trochlear notch
trochleaPositionX
X position of trochlear notch

Notes

Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the femoral cartilage. First flatten and fill any holes in the segmentation.

Expand source code
def getAnteriorOfWeightBearing(segArray, femurIndex=1):
    '''
    Prepare full segmentation and extract the trochlear notch location. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    femurIndex : int
        Index of the label used to localize the femur in the array. 
    Returns
    -------
    trochleaPositionY : 
        Y position of trochlear notch  
    trochleaPositionX : 
        X position of trochlear notch
    Notes
    -----
    Get the x/y coordinates for the trochlear notch. This is an iterative method that assumes things about the shape the femoral cartilage. 
    First flatten and fill any holes in the segmentation. 
    
    '''

    femurSegmentation = np.zeros_like(segArray)
    femurSegmentation[segArray == femurIndex] = 1
    flattenedSegmentation = np.amax(femurSegmentation, axis=1)
    flattened_seg_filled = ndi.binary_fill_holes(flattenedSegmentation)
    trochY, trochX = findNotch(flattened_seg_filled)
    return(trochY, trochX)
def getCartilageSubRegions(segArray, anteriorWBslice, posteriorWBslice, trochY, femurLabel=1, medTibiaLabel=2, latTibiaLabel=3, antFemurMask=5, medWbFemurMask=6, latWbFemurMask=7, medPostFemurMask=8, latPostFemurMask=9)

Take cartilage segmentation, and decompose femoral cartilage into subregions of interest.
Parameters


segArray : array
3D array with segmentation for the cartialge regions.
anteriorWBslice : int
Slice that seperates the anterior and weight bearing femoral cartilage.
posteriorWBslice : int
Slice that seperates the weight bearing and posterior femoral cartilage.
trochY : int
Slice that differentiates medial / lateral femur - trochlear notch Y component.
femurLabel : int
Label that femur is in the segArray
medTibiaLabel : int
Label that medial tibia is in the segArray
latTibiaLabel : int
Label that lateral tibia is in the segArray
antFemurMask : int
Label anterior femur should be labeled in final segmentation.
medWbFemurMask : int
Label medial weight bearing femur should be labeled in final segmentation.
latWbFemurMask : int
Label lateral weight bearing femur should be labeled in final segmentation.
medPostFemurMask : int
Label medial posterior femur should be labeled in final segmentation.
latPostFemurMask : int
Label lateral posterior femur should be labeled in final segmentation.

Returns

final_segmentation : array
3D array with the updated segmentations - including weightbearing, medial/latera, anterior, and posterior.

Notes

Expand source code
def getCartilageSubRegions(segArray, anteriorWBslice, posteriorWBslice, trochY,
                           femurLabel=1, medTibiaLabel=2, latTibiaLabel=3, antFemurMask=5, 
                           medWbFemurMask=6, latWbFemurMask=7, medPostFemurMask=8, latPostFemurMask=9):
    '''
    Take cartilage segmentation, and decompose femoral cartilage into subregions of interest.  
    Parameters
    ----------
    segArray : array
        3D array with segmentation for the cartialge regions. 
    anteriorWBslice : int
        Slice that seperates the anterior and weight bearing femoral cartilage.  
    posteriorWBslice : int
        Slice that seperates the weight bearing and posterior femoral cartilage. 
    trochY : int
        Slice that differentiates medial / lateral femur - trochlear notch Y component. 
    femurLabel : int
        Label that femur is in the segArray
    medTibiaLabel : int
        Label that medial tibia is in the segArray
    latTibiaLabel : int
        Label that lateral tibia is in the segArray
    antFemurMask : int
        Label anterior femur should be labeled in final segmentation. 
    medWbFemurMask : int
        Label medial weight bearing femur should be labeled in final segmentation.
    latWbFemurMask : int
        Label lateral weight bearing femur should be labeled in final segmentation. 
    medPostFemurMask : int
        Label medial posterior femur should be labeled in final segmentation. 
    latPostFemurMask : int
        Label lateral posterior femur should be labeled in final segmentation.
    Returns
    -------
    final_segmentation : array
        3D array with the updated segmentations - including weightbearing, medial/latera, anterior, and posterior. 
    Notes
    -----
    
    '''
    
    #array to store final segmentation
    final_segmentation = np.zeros_like(segArray)
    
    #create masks for ant/wb/posterior femur
    anterior_femur_mask = np.zeros_like(segArray)
    anterior_femur_mask[:,:,:anteriorWBslice] = 1

    wb_femur_mask = np.zeros_like(segArray)
    wb_femur_mask[:,:,anteriorWBslice:posteriorWBslice] = 1

    posterior_femur_mask = np.zeros_like(segArray)
    posterior_femur_mask[:,:,posteriorWBslice:] = 1
    
    #create seg of just femur - and then break it into the sub-regions
    femurSegArray = np.zeros_like(segArray)
    femurSegArray[segArray==femurLabel] = 1
    
    #find the center of the medial/lateral tibia - use to distinguish M/L femur ROIs
    locationMedialTibia = np.asarray(np.where(segArray==medTibiaLabel))
    locationLateralTibia = np.asarray(np.where(segArray==latTibiaLabel))
    
    centerMedialTibia = locationMedialTibia.mean(axis=1)
    centerLateralTibia = locationLateralTibia.mean(axis=1)

    med_femur_mask = np.zeros_like(segArray)
    lat_femur_mask = np.zeros_like(segArray)
    if centerMedialTibia[0] > trochY:
        med_femur_mask[trochY:,:,:] = 1
        lat_femur_mask[:trochY,:,:] = 1
    else:
        med_femur_mask[:trochY,:,:] = 1
        lat_femur_mask[trochY:,:,:] = 1

    final_segmentation[segArray!=femurLabel] = segArray[segArray!=femurLabel] 
    final_segmentation += (femurSegArray * anterior_femur_mask) * antFemurMask
    final_segmentation += (femurSegArray * wb_femur_mask * med_femur_mask) * medWbFemurMask
    final_segmentation += (femurSegArray * wb_femur_mask * lat_femur_mask) * latWbFemurMask
    final_segmentation += (femurSegArray * posterior_femur_mask * med_femur_mask) * medPostFemurMask
    final_segmentation += (femurSegArray * posterior_femur_mask * lat_femur_mask) * latPostFemurMask
    
    return(final_segmentation)
def get_knee_segmentation_with_femur_subregions(seg_image, fem_cart_label_idx=1, wb_region_percent_dist=0.6, med_tibia_label=2, lat_tibia_label=3, ant_femur_mask=11, med_wb_femur_mask=12, lat_wb_femur_mask=13, med_post_femur_mask=14, lat_post_femur_mask=15, verify_med_lat_tib_cart=True, tibia_label=6, ml_axis=0)

Give seg image of knee. Return seg image with all sub-regions of femur included.

Parameters

seg_image : SimpleITK.Image
SimpleITK image of the segmentation to be processed.
fem_cart_label_idx : int, optional
Label of femoral cartilage, by default 1
wb_region_percent_dist : float, optional
How large weightbearing region is (from not to posterior of condyles), by default 0.6
femur_label : int, optional
Seg label for the femur cartilage, by default 1
med_tibia_label : int, optional
Seg label for the medial tibia cartilage, by default 2
lat_tibia_label : int, optional
Seg label for the lateral tibia cartilage, by default 3
ant_femur_mask : int, optional
Seg label for the anterior femur region, by default 11
med_wb_femur_mask : int, optional
Seg label for medial weight-bearing femur, by default 12
lat_wb_femur_mask : int, optional
Seg label for lateral weight-bearing femur, by default 13
med_post_femur_mask : int, optional
Seg label for medial posterior femur, by default 14
lat_post_femur_mask : int, optional
Seg label for lateral posterior femur, by default 15
verify_med_lat_tib_cart : bool, optional
Whether to verify that medial and lateral tibial cartilage is on same side of centerline, by default True
tibia_label : int, optional
Seg label for the tibia, by default 6
ml_axis : int, optional
Medial/lateral axis of the acquired knee MRI, by default 0

Returns

SimpleITK.Image
Image of the new/updated segmentation
Expand source code
def get_knee_segmentation_with_femur_subregions(seg_image,
                                                fem_cart_label_idx=1,
                                                wb_region_percent_dist=0.6,
                                                # femur_label=1,
                                                med_tibia_label=2,
                                                lat_tibia_label=3,
                                                ant_femur_mask=11,
                                                med_wb_femur_mask=12,
                                                lat_wb_femur_mask=13,
                                                med_post_femur_mask=14,
                                                lat_post_femur_mask=15,
                                                verify_med_lat_tib_cart=True,
                                                tibia_label=6,
                                                ml_axis=0
                                                ):
    """
    Give seg image of knee. Return seg image with all sub-regions of femur included. 

    Parameters
    ----------
    seg_image : SimpleITK.Image
        SimpleITK image of the segmentation to be processed. 
    fem_cart_label_idx : int, optional
        Label of femoral cartilage, by default 1
    wb_region_percent_dist : float, optional
        How large weightbearing region is (from not to posterior of condyles), by default 0.6
    femur_label : int, optional
        Seg label for the femur cartilage, by default 1
    med_tibia_label : int, optional
        Seg label for the medial tibia cartilage, by default 2
    lat_tibia_label : int, optional
        Seg label for the lateral tibia cartilage, by default 3
    ant_femur_mask : int, optional
        Seg label for the anterior femur region, by default 11
    med_wb_femur_mask : int, optional
        Seg label for medial weight-bearing femur, by default 12
    lat_wb_femur_mask : int, optional
        Seg label for lateral weight-bearing femur, by default 13
    med_post_femur_mask : int, optional
        Seg label for medial posterior femur, by default 14
    lat_post_femur_mask : int, optional
        Seg label for lateral posterior femur, by default 15
    verify_med_lat_tib_cart : bool, optional
        Whether to verify that medial and lateral tibial cartilage is on same side of centerline, by default True
    tibia_label : int, optional
        Seg label for the tibia, by default 6
    ml_axis : int, optional
        Medial/lateral axis of the acquired knee MRI, by default 0

    Returns
    -------
    SimpleITK.Image
        Image of the new/updated segmentation
    """
    troch_notch_y, troch_notch_x = getAnteriorOfWeightBearing(sitk.GetArrayViewFromImage(seg_image),
                                                              femurIndex=fem_cart_label_idx)
    loc_fem_z, loc_fem_y, loc_fem_x = np.where(sitk.GetArrayViewFromImage(seg_image) == fem_cart_label_idx)
    post_femur_slice = np.max(loc_fem_x)
    posterior_wb_slice = np.round((post_femur_slice - troch_notch_x) * wb_region_percent_dist + troch_notch_x).astype(int)
    new_seg_array = getCartilageSubRegions(sitk.GetArrayViewFromImage(seg_image),
                                           anteriorWBslice=troch_notch_x,
                                           posteriorWBslice=posterior_wb_slice,
                                           trochY=troch_notch_y,
                                           femurLabel=fem_cart_label_idx,
                                           medTibiaLabel=med_tibia_label,
                                           latTibiaLabel=lat_tibia_label,
                                           antFemurMask=ant_femur_mask,
                                           medWbFemurMask=med_wb_femur_mask,
                                           latWbFemurMask=lat_wb_femur_mask,
                                           medPostFemurMask=med_post_femur_mask,
                                           latPostFemurMask=lat_post_femur_mask
                                           )

    if verify_med_lat_tib_cart:
        new_seg_array = verify_and_correct_med_lat_tib_cart(new_seg_array,
                                                            tib_label=tibia_label,
                                                            med_tib_cart_label=med_tibia_label, 
                                                            lat_tib_cart_label=lat_tibia_label,
                                                            ml_axis=ml_axis) 
    seg_label_image = sitk.GetImageFromArray(new_seg_array)
    seg_label_image.CopyInformation(seg_image)
    return seg_label_image
def get_y_CofM(flattenedSeg)

Get CofM of femoral cartilage for each row of the flattened segmentation. Parameters


flattenedSeg : 2D array
Axial flattened, and filled in femoral cartilage segmentation.

Returns

yCofM
Find the CofM for each row of the image.

Notes

Get the x/y coordinates for the CofM for each row of the flattened segmentation.

Expand source code
def get_y_CofM(flattenedSeg):
    '''
    Get CofM of femoral cartilage for each row of the flattened segmentation. 
    Parameters
    ----------
    flattenedSeg : 2D array
        Axial flattened, and filled in femoral cartilage segmentation.  
    Returns
    -------
    yCofM : 
        Find the CofM for each row of the image. 
    Notes
    -----
    Get the x/y coordinates for the CofM for each row of the flattened segmentation. 
    
    '''
    locationFemur = np.where(flattenedSeg==1)
    yCofM = np.zeros((flattenedSeg.shape[0], 2), dtype=int)
    
    # only calculate for rows with cartilage. 
    minRow = np.min(locationFemur[0])
    maxRow = np.max(locationFemur[0])
    
    # iterate over rows of image, get CofM, store CofM for row. 
    for x in range(minRow, maxRow):
        yCofM[x, 0] = x #store the x-coordinate (row) we calcualted CofM for. 
        yCofM[x, 1] = int(CofM(flattenedSeg[x, :])) # store the CofM value (make it an integer for indexing)
    yCofM = yCofM[minRow+10:maxRow-10,:] # remove 10 most medial and most lateral pixels of femoral cartilage. 
    return(yCofM) 
def verify_and_correct_med_lat_tib_cart(seg_array, tib_label=6, med_tib_cart_label=2, lat_tib_cart_label=3, ml_axis=0)

Verify that the medial and lateral tibial cartilage are correctly labeled. Parameters


seg_array : array
3D array with segmentation for the cartilage/bone regions.
tib_label : int
Label that tibial cartilage is in the seg_array
med_tib_cart_label : int
Label that medial tibial cartilage is in the seg_array
lat_tib_cart_label : int
Label that lateral tibial cartilage is in the seg_array
ml_axis : int
Medial/lateral axis of the acquired knee MRI.

Returns

seg_array : array
3D array with segmentation for the cartilage/bone regions. The tibial cartilage regions will have been updated to ensure all tib cart on med/lat sides are correctly classified.
Expand source code
def verify_and_correct_med_lat_tib_cart(
    seg_array,  #sitk.GetArrayViewFromImage(seg)
    tib_label=6,
    med_tib_cart_label=2, 
    lat_tib_cart_label=3,
    ml_axis=0
):
    '''
    Verify that the medial and lateral tibial cartilage are correctly labeled.
    Parameters
    ----------
    seg_array : array
        3D array with segmentation for the cartilage/bone regions.
    tib_label : int
        Label that tibial cartilage is in the seg_array
    med_tib_cart_label : int
        Label that medial tibial cartilage is in the seg_array
    lat_tib_cart_label : int
        Label that lateral tibial cartilage is in the seg_array
    ml_axis : int
        Medial/lateral axis of the acquired knee MRI.
    
    Returns
    -------
    seg_array : array
        3D array with segmentation for the cartilage/bone regions.
        The tibial cartilage regions will have been updated to ensure
        all tib cart on med/lat sides are correctly classified.
        
    '''
    #get binary array for tibia
    array_tib = np.zeros_like(seg_array)
    array_tib[seg_array == tib_label] = 1
    #get binary array for tib cart
    array_tib_cart = np.zeros_like(seg_array)
    array_tib_cart[(seg_array == lat_tib_cart_label) + (seg_array == med_tib_cart_label)] = 1

    #get the locatons of med/lat cartilage & get their centroids
    med_cart_locs = np.asarray(np.where(seg_array == med_tib_cart_label))
    lat_cart_locs = np.asarray(np.where(seg_array == lat_tib_cart_label))
    middle_med_cart = med_cart_locs[ml_axis,:].mean()
    middle_lat_cart = lat_cart_locs[ml_axis,:].mean()

    #get location of tibia to get centroid of tibial plateau
    tib_locs = np.asarray(np.where(seg_array == tib_label))
    middle_tib = tib_locs[ml_axis, :].mean()
    center_tibia_slice = int(middle_tib)

    # infer the direction(s) for medial/lateral
    med_direction = np.sign(middle_med_cart - middle_tib)
    lat_direction = np.sign(middle_lat_cart - middle_tib)
    if med_direction == lat_direction:
        raise Exception('Middle of med and lat tibial cartilage on same side of centerline!')

    #create med/lat cartilage masks - binary for updating seg masks
    med_tib_cart_mask = np.zeros_like(seg_array)
    lat_tib_cart_mask = np.zeros_like(seg_array)

    if med_direction > 0:
        med_tib_cart_mask[center_tibia_slice:,...] = 1
        lat_tib_cart_mask[:center_tibia_slice,...] = 1
    elif med_direction < 0:
        med_tib_cart_mask[:center_tibia_slice,...] = 1
        lat_tib_cart_mask[center_tibia_slice:,...] = 1

    # create new med/lat cartilage arrays 
    new_med_cart_array = array_tib_cart * med_tib_cart_mask
    new_lat_cart_array = array_tib_cart * lat_tib_cart_mask

    #make copy of original segmentation array & update
    # med/lat tibial cartilage labels
    new_seg_array = seg_array.copy()
    new_seg_array[new_med_cart_array == 1] = med_tib_cart_label
    new_seg_array[new_lat_cart_array == 1] = lat_tib_cart_label
    
    return new_seg_array