Classification of MRI Scans using Radiomics and MLP

baidehi18749746003 Last Updated : 30 Oct, 2024
8 min read

Tumors, which are abnormal growths that can develop on brain tissues, pose significant challenges to the Central Nervous System. To detect unusual activities in the brain, we rely on advanced medical imaging techniques like MRI and CT scans. However, accurately identifying tumors can be complex due to their diverse shapes and textures, requiring careful analysis by medical professionals. This is where the power of MRI scans using radiomics comes into play. By implementing handcrafted feature extraction followed by classification techniques, we can enhance the speed and efficiency with which doctors analyze imaging data, ultimately leading to more precise diagnoses and improved patient outcomes.

Learning Objectives

  • Diving deep into the domain of handcrafted features.
  • Understanding the importance of Radiomics in extracting handcrafted features. 
  • Gain insights into how MRI scans using radiomics improve tumor detection and classification, enabling more accurate medical diagnoses.
  • Using the extracted features to classify into different classes. 
  • Leveraging the power of Radiomics and Multi Layer Perceptron for classification. 

This article was published as a part of the Data Science Blogathon.

Understanding Radiomics for Feature Extraction

Radiomics is the technique that is used in the medical field to detect the handcrafted features. By handcrafted features, we mean the texture, density, intensities etc. These features are helpful as they help to understand the complex patterns of the diseases. It basically makes use of mathematical and statistical operations to calculate the feature values. The final values provide us with the deep insights that can be later used for further clinical observations. Here we need to note one thing. The feature extraction is basically done on the Region of Interest. 

Common Radiomic Features for Tumor Detection

Here we will discuss about the features that are extracted using Radiomics. Some of them are as follows:

  • Shape Features: In this Radiomics extracts the geometric features of the Region of interest. It includes volume, Area, Length, broadness, Compactness etc. 
  • Statistical Features: As the name suggests, it makes use of statistical techniques like mean, standard deviation, skew, Kurtosis, Randomness. Using these we can evaluate the intensity of ROI. 
  • Texture Features: These features focuses on the homogeneity and heterogeneity of the surface of the Region of Interest. Some examples are as follows:
    • GLCM or Gray Level Co-occurrence Matrix: Measures the contrast, correlation of the pixels or voxels in the ROI
    • GLZSM or Gray Level Zone Size Matrix: It is used to calculate the zonal percentage of the homogeneous areas in the ROI. 
    • GLRLM or Gray Level Run Length Matrix: Used to measure the uniformity of the intensities across the Region of interest.
  • Advanced Mathematical features: Advanced mathematical techniques like Laplacian, Gaussian, and Gradient formulas capture patterns in depth by applying filters.

Dataset Overview

Here we will be using the brain tumor dataset that is present on Kaggle. The link to download the dataset is here. The dataset has two categories or classes: yes or no. Each class has 1500 images.

  • yes denotes the presence of the tumour. 
  • no denotes that the tumour is not present. 

Below are some sample images:

MRI Scans using Radiomics
tumour images

Environment Setup and Libraries

We use the PyRadiomics library to extract features, and we’ve chosen Google Colab for this process since it provides the latest Python version, ensuring PyRadiomics runs smoothly. Using older versions may otherwise cause errors. Apart from PyRadiomics we have used other libraries like SITK, Numpy, Torch for creating Multi Layer Perceptrons. We have also used Pandas to store the features in the dataframe.

Feature Extraction Process Using Radiomics

As discussed earlier, we will be using the brain tumor dataset. But here masks are not present that can be used to highlight the brain tissue which is our Region of Interest. So we will create binary masks and extract features from the masked region. So first we will load the image dataset using OS library and create a dataframe that comprises image paths and labels. 

# 1. Import necessary libraries
import os
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
from radiomics import featureextractor
import SimpleITK as sitk

# 2. Mount Google Drive
from google.colab import drive
drive.mount('/content/drive')

# 3. Define the dataset path
base_path = '/content/drive/MyDrive/brain'

# 4. Prepare a DataFrame with image paths and labels
data = []
for label in ['yes', 'no']:
    folder_path = os.path.join(base_path, label)
    for filename in os.listdir(folder_path):
        if filename.endswith(('.png', '.jpg', '.jpeg')):  # Ensure you're reading image files
            image_path = os.path.join(folder_path, filename)
            data.append({'image_path': image_path, 'label': label})

df = pd.DataFrame(data)

We’ll use the Simple Image Tool Kit (SITK) library to read images, as SITK preserves voxel intensities and orientation—features not maintained by OpenCV or Pillow. Additionally, SITK is supported by Radiomics, ensuring consistency. After reading the image, we convert it to grayscale and create a binary mask using Otsu thresholding, which provides optimal values for grayscale images. Finally, we extract the radiomic features, label each feature as “yes” or “no,” store them in a list, and convert the list into a DataFrame.

# 5. Initialize the Radiomics feature extractor
extractor = featureextractor.RadiomicsFeatureExtractor()
k=0
# 6. Extract features from images
features_list = []
for index, row in df.iterrows():
    image_path = row['image_path']
    label = row['label']

    # Load image
    image_sitk = sitk.ReadImage(image_path)

    # Convert image to grayscale if it is an RGB image
    if image_sitk.GetNumberOfComponentsPerPixel() > 1:  # Check if the image is color (RGB)
        image_sitk = sitk.VectorIndexSelectionCast(image_sitk, 0)  # Use the first channel (grayscale)

    # Apply Otsu threshold to segment brain from background
    otsu_filter = sitk.OtsuThresholdImageFilter()
    mask_sitk = otsu_filter.Execute(image_sitk)  # Create binary mask using Otsu's method

    # Ensure the mask has the same metadata as the image
    mask_sitk.CopyInformation(image_sitk)

    # Extract features using the generated mask
    features = extractor.execute(image_sitk, mask_sitk)
    features['label'] = label  # Add label to features
    features_list.append(features)
    print(k)
    k+=1

# 7. Convert extracted features into a DataFrame
features_df = pd.DataFrame(features_list)

# 8. Split the dataset into training and testing sets
X = features_df.drop(columns=['label'])  # Features
y = features_df['label']  # Labels

Preprocessing the Feature Data

When Radiomics extracts the features from images, it also appends version of the functions to the feature arrays. So we need to include those feature values that has feature name with ‘original_’. For non numeric feature values, we coerce and later fill that data with 0. For the labels part we are converting the strings to 0 or 1. After that we divide the data into train and test in the ratio 80:20. Lastly the features are standardized using StandardScaler. We also check if the classes are imbalanced or not.

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import pandas as pd
import matplotlib.pyplot as plt

# Assuming features_df is already defined and processed

feature_cols = [col for col in features_df.columns if col.startswith('original_')]

# Convert the selected columns to numeric, errors='coerce' will replace non-numeric values with NaN
features_df[feature_cols] = features_df[feature_cols].applymap(lambda x: x.item() if hasattr(x, 'item') else x).apply(pd.to_numeric, errors='coerce')

# Replace NaN values with 0 (you can use other strategies if appropriate)
features_df = features_df.fillna(0)

# Split the dataset into training and testing sets
X = features_df[feature_cols].values  # Features as NumPy array
y = features_df['label'].map({'yes': 1, 'no': 0}).values  # Labels as NumPy array (0 or 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)


class_counts = pd.Series(y_train).value_counts()

# Get the majority and minority classes
majority_class = class_counts.idxmax()
minority_class = class_counts.idxmin()
majority_count = class_counts.max()
minority_count = class_counts.min()

print(f'Majority Class: {majority_class} with count: {majority_count}')
print(f'Minority Class: {minority_class} with count: {minority_count}')
output

Using Multi-Layer Perceptron for Classification 

In this step, we will create a Multi Layer Perceptron. But before that we convert the train and test data to tensors. DataLoaders are also created with batch size 32.

X_train_tensor = torch.tensor(X_train, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.long)
X_test_tensor = torch.tensor(X_test, dtype=torch.float32)
y_test_tensor = torch.tensor(y_test, dtype=torch.long)

# Create PyTorch datasets and dataloaders
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
test_dataset = TensorDataset(X_test_tensor, y_test_tensor)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)  # Adjust batch size as needed
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

The MLP defined below has two hidden layers, ReLU as activation function and Dropout rate is 50%. The loss function used is Cross Entropy Loss and the optimizer used is Adam with learning rate of 0.001. 

class MLP(nn.Module):
    def __init__(self, input_size, hidden_size1, hidden_size2, output_size):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size1)
        self.relu1 = nn.ReLU()
        self.dropout1 = nn.Dropout(0.5)  # Dropout layer with 50% dropout rate
        self.fc2 = nn.Linear(hidden_size1, hidden_size2)
        self.relu2 = nn.ReLU()
        self.dropout2 = nn.Dropout(0.5)
        self.fc3 = nn.Linear(hidden_size2, output_size)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.dropout1(x)
        x = self.fc2(x)
        x = self.relu2(x)
        x = self.dropout2(x)
        x = self.fc3(x)
        return x

# Create an instance of the model
input_size = X_train.shape[1]  # Number of features
hidden_size1 = 128  # Adjust hidden layer sizes as needed
hidden_size2 = 64
output_size = 2  # Binary classification (yes/no)
model = MLP(input_size, hidden_size1, hidden_size2, output_size)

# Define loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)  # Adjust learning rate as needed

# Initialize a list to store loss values
loss_values = []

# Train the model
epochs = 200  # Adjust number of epochs as needed
for epoch in range(epochs):
    model.train()  # Set model to training mode
    running_loss = 0.0

    for i, (inputs, labels) in enumerate(train_loader):
        # Zero the gradients
        optimizer.zero_grad()

        # Forward pass
        outputs = model(inputs)

        # Compute the loss
        loss = criterion(outputs, labels)

        # Backward pass and optimization
        loss.backward()
        optimizer.step()

        # Accumulate the running loss
        running_loss += loss.item()

    # Store average loss for this epoch
    avg_loss = running_loss / len(train_loader)
    loss_values.append(avg_loss)  # Append to loss values
    print(f"Epoch [{epoch+1}/{epochs}], Loss: {avg_loss:.4f}")

# Test the model after training
model.eval()  # Set model to evaluation mode
correct = 0
total = 0

with torch.no_grad():  # Disable gradient computation for testing
    for inputs, labels in test_loader:
        outputs = model(inputs)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

# Calculate and print accuracy
accuracy = 100 * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')

# Plot the Loss Graph
plt.figure(figsize=(10, 5))
plt.plot(loss_values, label='Training Loss', color='blue')
plt.title('Training Loss Curve')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid()
plt.show()

As we can see the model is trained for 200 epochs and the loss is recorded at each epoch which will be later used for plotting. The optimizer is used to optimize the weights. Now we will test the model by disabling the gradient calculations. 

output: MRI Scans using Radiomics

As we can see from the below output, the accuracy is 94.50% on the testing dataset. From this we can conclude that the model generalizes well based on the radiomic features.

Conclusion

Leveraging Radiomics and Multi-Layer Perceptrons (MLP) in brain tumor classification can streamline and enhance the diagnostic process for medical professionals. By extracting handcrafted features from brain imaging, we can capture subtle patterns and characteristics that aid in accurately identifying tumor presence. This approach minimizes the need for manual analysis, allowing doctors to make informed, data-driven decisions more quickly. The integration of feature extraction with MLP classification demonstrates the potential of AI in medical imaging, presenting an efficient, scalable solution that could greatly support radiologists and healthcare providers in diagnosing complex cases.

Click here for google collab link.

Key Takeaways

  • Radiomics captures detailed imaging features, enabling more precise brain tumor analysis.
  • Multi-Layer Perceptrons (MLPs) improve classification accuracy by processing complex data patterns.
  • Feature extraction and MLP integration streamline brain tumor detection, aiding in faster diagnosis.
  • Combining AI with radiology offers a scalable approach to support healthcare professionals.
  • This technique exemplifies how AI can enhance diagnostic efficiency and accuracy in medical imaging.

Frequently Asked Questions

Q1. What is radiomics in brain tumor analysis?

A. Radiomics involves extracting quantitative data from medical images, offering detailed insights into tumor characteristics.

Q2. Why are Multi-Layer Perceptrons (MLPs) used in classification?

A. MLPs can recognize complex patterns in data, improving the accuracy of tumor classification.

Q3. How does AI support brain tumor detection?

A. AI processes and interprets vast imaging data, enabling faster and more accurate tumor identification.

Q4. What are the benefits of feature extraction in radiomics?

A. Feature extraction highlights specific tumor traits, enhancing diagnostic precision.

Q5. What is the role of Radiomics in analyzing MRI scans?

A. Radiomics plays a crucial role in analyzing MRI scans by extracting quantitative features from medical images, which can reveal patterns and biomarkers. This information enhances diagnostic accuracy, aids in treatment planning, and allows for personalized medicine by providing insights into tumor characteristics and responses to therapy.

The media shown in this article is not owned by Analytics Vidhya and is used at the Author’s discretion.

Responses From Readers

Clear

Congratulations, You Did It!
Well Done on Completing Your Learning Journey. Stay curious and keep exploring!

We use cookies essential for this site to function well. Please click to help us improve its usefulness with additional cookies. Learn about our use of cookies in our Privacy Policy & Cookies Policy.

Show details