Help with UNQ_C1 A14M Course 1 Week 3

I’ve spent a lot of time on this, but I’m still unable to get the expected output. I would appreciate any help/explanation of the code. Please see my code below. I’m pretty sure I made a lot of mistakes.

UNQ_C1 (UNIQUE CELL IDENTIFIER, DO NOT EDIT)

def get_sub_volume(image, label,
orig_x = 240, orig_y = 240, orig_z = 155,
output_x = 160, output_y = 160, output_z = 16,
num_classes = 4, max_tries = 1000,
background_threshold=0.95):
“”"
Extract random sub-volume from original images.

Args:
    image (np.array): original image, 
        of shape (orig_x, orig_y, orig_z, num_channels)
    label (np.array): original label. 
        labels coded using discrete values rather than
        a separate dimension, 
        so this is of shape (orig_x, orig_y, orig_z)
    orig_x (int): x_dim of input image
    orig_y (int): y_dim of input image
    orig_z (int): z_dim of input image
    output_x (int): desired x_dim of output
    output_y (int): desired y_dim of output
    output_z (int): desired z_dim of output
    num_classes (int): number of class labels
    max_tries (int): maximum trials to do when sampling
    background_threshold (float): limit on the fraction 
        of the sample which can be the background

returns:
    X (np.array): sample of original image of dimension 
        (num_channels, output_x, output_y, output_z)
    y (np.array): labels which correspond to X, of dimension 
        (num_classes, output_x, output_y, output_z)
"""
# Initialize features and labels with `None`
X = image
y = label

### START CODE HERE (REPLACE INSTANCES OF 'None' with your code) ###

tries = 0

while tries < max_tries:
    # randomly sample sub-volume by sampling the corner voxel
    # hint: make sure to leave enough room for the output dimensions!
    start_x = 240
    start_y = 240
    start_z = 240

    # extract relevant area of label
    y = label[start_x: start_x + output_x,
              start_y: start_y + output_y,
              start_z: start_z + output_z]
    
    # One-hot encode the categories.
    # This adds a 4th dimension, 'num_classes'
    # (output_x, output_y, output_z, num_classes)
    y = keras.utils.to_categorical(np.random.randint(0, 4, (240, 240, 155)), num_classes=4)

    # compute the background ratio
    bgrd_ratio = np.count_nonzero(np.random.randint(0, 4, (240, 240, 155)) == 0) / len(np.random.randint(0, 4, (240, 240, 155)))

    # increment tries counter
    tries += 1

    # if background ratio is below the desired threshold,
    # use that sub-volume.
    # otherwise continue the loop and try another random sub-volume
    if bgrd_ratio < background_threshold:

        # make copy of the sub-volume
        X = np.copy(image[start_x: start_x + output_x,
                          start_y: start_y + output_y,
                          start_z: start_z + output_z, :])
        
        # change dimension of X
        # from (x_dim, y_dim, z_dim, num_channels)
        # to (num_channels, x_dim, y_dim, z_dim)
        X = (3, 240, 240, 155)

        # change dimension of y
        # from (x_dim, y_dim, z_dim, num_classes)
        # to (num_classes, x_dim, y_dim, z_dim)
        y = (155, 240, 240)

        ### END CODE HERE ###
        
        # take a subset of y that excludes the background class
        # in the 'num_classes' dimension
        y = y[1:, :, :, :]

        return X, y

# if we've tried max_tries number of samples
# Give up in order to avoid looping forever.
print(f"Tried {tries} times to find a sub-volume. Giving up...")

Hi niheon

Let’s take it one step at a time :slight_smile:

The first thing you are asked to do is randomly select a voxel that will be used as the start corner of a volume. This is exactly the same as what we did in the week 3 lab in the section " Random selection of start indices". The only difference is that now we need 3 indices since we are working in 3 dimensions. Please go back to the lab and see if it makes sense to you, otherwise we can go through this topic again :slight_smile:

Then you are asked to one-hot encode the categories. You are on the right track but when you call np.random.randint inside the keras.utils.to_categorical you are just creating a new random vector. What you want to one-hot encode is the labels.
The same thing applies to the next line to compute the background ratio. This is also the same exercise as in this week’s lab, so please review the lab again. Here think about how you can identify the background voxels. Additional hint: in the lab we used len to determine the size of the patch because we were working in 1D. But now we are working in 3D. What will be the size of the selected volume now? :wink:

Finally, when you are asked to change the dimensions of X and y, the current code is just assigning a tuple to these variables, which is not what we want. Have a look at the hint above the exercise to have an idea of how you can change the shape of these vectors in python. Also remember that you want this function to work for volumes of any size so the dimensions should not be hard coded in the function (that is, the numbers should not be defined in the function but rather be retrieved from the inputs).

I hope this helps! :slight_smile:

3 Likes

Hi @margaridacosta,

Thank you for replying. I appreciate it.

I made a video to show you how I’m getting the code, which is still wrong. Please see the video.

Wow thank you for the detailed video! :smiley: I understand where your confusion comes from.

To answer your question: during the lab 2 exercise, patch_length = 3 was just an example, yes. But now here we want to get a “patch” of a specific size as specified in the function’s input. You’re on the right track when thinking about each dimension (x, y and z) individually. But be careful that when you use image.shape[0] you are only getting the first dimension of image (x dimension). Have a look at the function’s inputs again. Maybe there is a better way to get this information?

Regarding your doubt in the one-hot encoding section: the patch_labels values represent the class to which each input value belongs to:

  • 0: background
  • 1: edema
  • 2: non-enhancing tumor
  • 3: enhancing tumor

Hence in MRI setting each element in the labels (y) vector will tell us the class of the equivalent element or voxel in the MRI volume/image (X). (This was only random in the lab exercise because it was just a “dummy” example. There were no real labels)

In the background ratio calculation section there is still a small problem. Remember that you’ve already done one-hot encoding - selecting np.count_nonzero(y==0 ) would only work if you still had the actual classes (the original y vector). It’s a bit hard to explain in writing, sorry - please have a look again at the second lab (at the very bottom) to understand what the one-hot encoding step is doing and how you can get the background ratio from the one-hot encoded labels.

Other than that your code looks about right!

1 Like

I appreciate your patience and your help. Could you please check. I think I got it, but I’m still not getting the expected output.

UNQ_C1 (UNIQUE CELL IDENTIFIER, DO NOT EDIT)

def get_sub_volume(image, label,
orig_x = 240, orig_y = 240, orig_z = 155,
output_x = 160, output_y = 160, output_z = 16,
num_classes = 4, max_tries = 1000,
background_threshold=0.95):
“”"
Extract random sub-volume from original images.

Args:
    image (np.array): original image, 
        of shape (orig_x, orig_y, orig_z, num_channels)
    label (np.array): original label. 
        labels coded using discrete values rather than
        a separate dimension, 
        so this is of shape (orig_x, orig_y, orig_z)
    orig_x (int): x_dim of input image
    orig_y (int): y_dim of input image
    orig_z (int): z_dim of input image
    output_x (int): desired x_dim of output
    output_y (int): desired y_dim of output
    output_z (int): desired z_dim of output
    num_classes (int): number of class labels
    max_tries (int): maximum trials to do when sampling
    background_threshold (float): limit on the fraction 
        of the sample which can be the background

returns:
    X (np.array): sample of original image of dimension 
        (num_channels, output_x, output_y, output_z)
    y (np.array): labels which correspond to X, of dimension 
        (num_classes, output_x, output_y, output_z)
"""
# Initialize features and labels with `None`
X = image
y = label

### START CODE HERE (REPLACE INSTANCES OF 'None' with your code) ###

tries = 0

while tries < max_tries:
    # randomly sample sub-volume by sampling the corner voxel
    # hint: make sure to leave enough room for the output dimensions!
    start_x = np.random.randint(0, orig_x - output_x + 1)
    start_y = np.random.randint(0, orig_y - output_y + 1)
    start_z = np.random.randint(0, orig_z - output_z + 1)

    # extract relevant area of label
    y = label[start_x: start_x + output_x,
              start_y: start_y + output_y,
              start_z: start_z + output_z]
    
    # One-hot encode the categories.
    # This adds a 4th dimension, 'num_classes'
    # (output_x, output_y, output_z, num_classes)
    y = keras.utils.to_categorical(y, num_classes=num_classes)

    # compute the background ratio
    bgrd_ratio = np.sum(y[:, :, :, 0])/(output_x * output_y * output_z)

    # increment tries counter
    tries += 1

    # if background ratio is below the desired threshold,
    # use that sub-volume.
    # otherwise continue the loop and try another random sub-volume
    if bgrd_ratio < background_threshold:

        # make copy of the sub-volume
        X = np.copy(image[start_x: start_x + output_x,
                          start_y: start_y + output_y,
                          start_z: start_z + output_z, :])
        
        # change dimension of X
        # from (x_dim, y_dim, z_dim, num_channels)
        # to (num_channels, x_dim, y_dim, z_dim)
        X = np.moveaxis(X, -1, 0)

        # change dimension of y
        # from (x_dim, y_dim, z_dim, num_classes)
        # to (num_classes, x_dim, y_dim, z_dim)
        y = np.moveaxis(y, -1, 0)

        ### END CODE HERE ###
        
        # take a subset of y that excludes the background class
        # in the 'num_classes' dimension
        y = y[1:, :, :, :]

        return X, y

# if we've tried max_tries number of samples
# Give up in order to avoid looping forever.
print(f"Tried {tries} times to find a sub-volume. Giving up...")

mention axis in bgrd_ratio = np.sum(y[:, :, :, 0])/(output_x * output_y * output_z)

@niheon Remember that we are trying to get random sections of the MRI volume. So it’s not a problem if the values that you get are not exactly the same as those in the expected output section. What matters is that the dimensions are correct and that the section you got is a subsection of the original image.