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.
This article was published as a part of the Data Science Blogathon.
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.
Here we will discuss about the features that are extracted using Radiomics. Some of them are as follows:
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.
Below are some sample images:
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.
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
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}')
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.
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.
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.
A. Radiomics involves extracting quantitative data from medical images, offering detailed insights into tumor characteristics.
A. MLPs can recognize complex patterns in data, improving the accuracy of tumor classification.
A. AI processes and interprets vast imaging data, enabling faster and more accurate tumor identification.
A. Feature extraction highlights specific tumor traits, enhancing diagnostic precision.
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.