diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..bceafdf2f24771ce70ec25d65db22f31a3ff09b7 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/BrainIAC/.DS_Store b/src/BrainIAC/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7fa5d5ac21d1293a5178d86b0ba8df92d22b5b96 Binary files /dev/null and b/src/BrainIAC/.DS_Store differ diff --git a/src/BrainIAC/Brainage/README.md b/src/BrainIAC/Brainage/README.md new file mode 100644 index 0000000000000000000000000000000000000000..44da0a58db45468ee4a26d679d34f19b1214b4b6 --- /dev/null +++ b/src/BrainIAC/Brainage/README.md @@ -0,0 +1,55 @@ +# Brain Age Prediction + +

+ Brain Age Prediction Example +

+ +## Overview + +We present the brainage prediction training and inference code for BrainIAC as a downstream task. The pipeline is trained and infered on T1 scans, with MAE as evaluation metric. + +## Data Requirements + +- **Input**: T1-weighted MRI scans +- **Format**: NIFTI (.nii.gz) +- **Preprocessing**: Bias field corrected, registered to standard space, skull stripped +- **CSV Structure**: + ``` + pat_id,scandate,label + subject001,20240101,65 # brain age in years + ``` +refer to [ quickstart.ipynb](../quickstart.ipynb) to find how to preprocess data and generate csv file. + + +## Setup + +1. **Configuration**: +change the [config.yml](../config.yml) file accordingly. + ```yaml + # config.yml + data: + train_csv: "path/to/train.csv" + val_csv: "path/to/val.csv" + test_csv: "path/to/test.csv" + root_dir: "../data/sample/processed" + collate: 1 # single scan framework + + checkpoints: "./checkpoints/brainage_model.00" # for inference/testing + + train: + finetune: 'yes' # yes to finetune the entire model + freeze: 'no' # yes to freeze the resnet backbone + weights: ./checkpoints/brainiac.ckpt # path to brainiac weights + + ``` + +2. **Training**: + ```bash + python -m Brainage.train_brainage + ``` + +3. **Inference**: + ```bash + python -m Brainage.infer_brainage + ``` + diff --git a/src/BrainIAC/Brainage/__init__.py b/src/BrainIAC/Brainage/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/Brainage/__pycache__/__init__.cpython-39.pyc b/src/BrainIAC/Brainage/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f8f3bdd434ee28d43e0efa276c1a1513241c87 Binary files /dev/null and b/src/BrainIAC/Brainage/__pycache__/__init__.cpython-39.pyc differ diff --git a/src/BrainIAC/Brainage/__pycache__/infer_brainage.cpython-39.pyc b/src/BrainIAC/Brainage/__pycache__/infer_brainage.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ae66dbb31ddbe8599c89b9d856ce91f9f3d1943 Binary files /dev/null and b/src/BrainIAC/Brainage/__pycache__/infer_brainage.cpython-39.pyc differ diff --git a/src/BrainIAC/Brainage/brainage.jpeg b/src/BrainIAC/Brainage/brainage.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fba11c700dab04b45d5cf5acd64ebf142ea76389 --- /dev/null +++ b/src/BrainIAC/Brainage/brainage.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b844af61b1dea2e772edfddcd8f8adb0453721f7972684b7b580e85ae2addf5 +size 33514 diff --git a/src/BrainIAC/Brainage/infer_brainage.py b/src/BrainIAC/Brainage/infer_brainage.py new file mode 100644 index 0000000000000000000000000000000000000000..306bb31a0af9aac6ec78acfd840b17bb1f2c1fbc --- /dev/null +++ b/src/BrainIAC/Brainage/infer_brainage.py @@ -0,0 +1,85 @@ +import torch +import pandas as pd +import os +from tqdm import tqdm +from torch.utils.data import DataLoader +from torch.cuda.amp import autocast +from sklearn.metrics import mean_absolute_error +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from dataset2 import MedicalImageDatasetBalancedIntensity3D +from model import Backbone, SingleScanModel, Classifier +from utils import BaseConfig + +class BrainAgeInference(BaseConfig): + """ + Inference class for brain age prediction model. + """ + + def __init__(self): + """Initialize the inference setup with model and data.""" + super().__init__() + self.setup_model() + self.setup_data() + + def setup_model(self): + config = self.get_config() + self.backbone = Backbone() + self.classifier = Classifier(d_model=2048) + self.model = SingleScanModel(self.backbone, self.classifier) + + # Load weights + checkpoint = torch.load(config["infer"]["checkpoints"], map_location=self.device) + self.model.load_state_dict(checkpoint["model_state_dict"]) + self.model = self.model.to(self.device) + self.model.eval() + print("Model and checkpoint loaded!") + + ## spinup dataloaders + def setup_data(self): + config = self.get_config() + self.test_dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=config["data"]["test_csv"], + root_dir=config["data"]["root_dir"] + ) + self.test_loader = DataLoader( + self.test_dataset, + batch_size=1, + shuffle=False, + collate_fn=self.custom_collate, + num_workers=1 + ) + + def infer(self): + """ Infer pass """ + results_df = pd.DataFrame(columns=['PredictedAge', 'TrueAge']) + all_labels = [] + all_predictions = [] + + with torch.no_grad(): + for sample in tqdm(self.test_loader, desc="Inference", unit="batch"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + with autocast(): + outputs = self.model(inputs) + + predictions = outputs.cpu().numpy().flatten() + all_labels.extend(labels.cpu().numpy().flatten()) + all_predictions.extend(predictions) + + result = pd.DataFrame({ + 'PredictedAge': predictions, + 'TrueAge': labels.cpu().numpy().flatten() + }) + results_df = pd.concat([results_df, result], ignore_index=True) + + mae = mean_absolute_error(all_labels, all_predictions) + print(f"Mean Absolute Error (MAE): {mae:.4f} months") + results_df.to_csv('./data/output/brainage_output.csv', index=False) + + return mae + +if __name__ == "__main__": + inferencer = BrainAgeInference() + mae = inferencer.infer() \ No newline at end of file diff --git a/src/BrainIAC/Brainage/train_brainage.py b/src/BrainIAC/Brainage/train_brainage.py new file mode 100644 index 0000000000000000000000000000000000000000..4f8556d9cc4714c3c283c402ade3616a9bc57f66 --- /dev/null +++ b/src/BrainIAC/Brainage/train_brainage.py @@ -0,0 +1,230 @@ +import torch +import torch.nn as nn +import torch.optim as optim +from torch.utils.data import DataLoader +import wandb +from tqdm import tqdm +from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts +from torch.cuda.amp import GradScaler, autocast +from sklearn.metrics import mean_absolute_error +import os +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from dataset2 import MedicalImageDatasetBalancedIntensity3D, TransformationMedicalImageDatasetBalancedIntensity3D +from model import Backbone, SingleScanModel, Classifier +from utils import BaseConfig + + +class BrainAgeTrainer(BaseConfig): + """ + A trainer class for brain age prediction models. + + This class handles the complete training pipeline including model setup, + data loading, training loop, and validation. + Inherits from BaseConfig for configuration management. + """ + + def __init__(self): + """Initialize the trainer with model, data, and training setup.""" + super().__init__() + self.setup_wandb() + self.setup_model() + self.setup_data() + self.setup_training() + + ## setup wandb logger + def setup_wandb(self): + config = self.get_config() + wandb.init( + project=config['logger']['project_name'], + name=config['logger']['run_name'], + config=config + ) + + def setup_model(self): + """ + Set up the model architecture. + + Initializes the backbone and classifier blocks, and loads + checkpoints + """ + self.backbone = Backbone() + self.classifier = Classifier(d_model=2048) + self.model = SingleScanModel(self.backbone, self.classifier) + + # Load BrainIACs weights + config = self.get_config() + if config["train"]["finetune"] == "yes": + checkpoint = torch.load(config["train"]["weights"], map_location=self.device) + state_dict = checkpoint["state_dict"] + filtered_state_dict = {} + for key, value in state_dict.items(): + new_key = key.replace("module.", "backbone.") if key.startswith("module.") else key + filtered_state_dict[new_key] = value + self.model.backbone.load_state_dict(filtered_state_dict, strict=False) + print("Pretrained weights loaded!") + + # Freeze backbone if specified + if config["train"]["freeze"] == "yes": + for param in self.model.backbone.parameters(): + param.requires_grad = False + print("Backbone weights frozen!") + + self.model = self.model.to(self.device) + + def setup_data(self): + """ + Set up data loaders for training and validation. + Inherit configuration from the base config + """ + config = self.get_config() + self.train_dataset = TransformationMedicalImageDatasetBalancedIntensity3D( + csv_path=config['data']['train_csv'], + root_dir=config["data"]["root_dir"] + ) + self.val_dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=config['data']['val_csv'], + root_dir=config["data"]["root_dir"] + ) + + self.train_loader = DataLoader( + self.train_dataset, + batch_size=config["data"]["batch_size"], + shuffle=True, + collate_fn=self.custom_collate, + num_workers=config["data"]["num_workers"] + ) + self.val_loader = DataLoader( + self.val_dataset, + batch_size=1, + shuffle=False, + collate_fn=self.custom_collate, + num_workers=1 + ) + + def setup_training(self): + """ + Set up training config with loss, scheduler, optimizer. + """ + config = self.get_config() + self.criterion = nn.MSELoss() + self.optimizer = optim.Adam( + self.model.parameters(), + lr=config['optim']['lr'], + weight_decay=config["optim"]["weight_decay"] + ) + self.scheduler = CosineAnnealingWarmRestarts(self.optimizer, T_0=50, T_mult=2) + self.scaler = GradScaler() + + def train(self): + """ + main training loop + """ + config = self.get_config() + max_epochs = config['optim']['max_epochs'] + best_val_loss = float('inf') + best_val_mae = float('inf') + + for epoch in range(max_epochs): + train_loss = self.train_epoch(epoch, max_epochs) + val_loss, mae = self.validate_epoch(epoch, max_epochs) + + # Save best model + if (val_loss <= best_val_loss) and (mae <= best_val_mae): + print(f"Improved Val Loss from {best_val_loss:.4f} to {val_loss:.4f}") + print(f"Improved Val MAE from {best_val_mae:.4f} to {mae:.4f}") + best_val_loss = val_loss + best_val_mae = mae + self.save_checkpoint(epoch, val_loss, mae) + + wandb.finish() + + def train_epoch(self, epoch, max_epochs): + """ + Train pass. + + Args: + epoch (int): Current epoch number + max_epochs (int): Total number of epochs + + Returns: + float: Average training loss for the epoch + """ + self.model.train() + train_loss = 0.0 + + for sample in tqdm(self.train_loader, desc=f"Training Epoch {epoch}/{max_epochs-1}"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + self.optimizer.zero_grad() + with autocast(): + outputs = self.model(inputs) + loss = self.criterion(outputs, labels.unsqueeze(1)) + + self.scaler.scale(loss).backward() + self.scaler.step(self.optimizer) + self.scaler.update() + + train_loss += loss.item() * inputs.size(0) + + train_loss = train_loss / len(self.train_loader.dataset) + wandb.log({"Train Loss": train_loss}) + return train_loss + + def validate_epoch(self, epoch, max_epochs): + """ + Validation pass. + + Args: + epoch (int): Current epoch number + max_epochs (int): Total number of epochs + + Returns: + tuple: (validation_loss, mean_absolute_error) + """ + self.model.eval() + val_loss = 0.0 + all_labels = [] + all_preds = [] + + with torch.no_grad(): + for sample in tqdm(self.val_loader, desc=f"Validation Epoch {epoch}/{max_epochs-1}"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + outputs = self.model(inputs) + loss = self.criterion(outputs, labels.unsqueeze(1)) + + val_loss += loss.item() * inputs.size(0) + all_labels.extend(labels.cpu().numpy().flatten()) + all_preds.extend(outputs.cpu().numpy().flatten()) + + val_loss = val_loss / len(self.val_loader.dataset) + mae = mean_absolute_error(all_labels, all_preds) + + wandb.log({"Val Loss": val_loss, "MAE": mae}) + self.scheduler.step(val_loss) + + print(f"Epoch {epoch}/{max_epochs-1} Val Loss: {val_loss:.4f} MAE: {mae:.4f}") + return val_loss, mae + + def save_checkpoint(self, epoch, loss, mae): + """ + Save model checkpoint. + """ + config = self.get_config() + checkpoint = { + 'model_state_dict': self.model.state_dict(), + 'loss': loss, + 'epoch': epoch, + } + save_path = os.path.join( + config['logger']['save_dir'], + config['logger']['save_name'].format(epoch=epoch, loss=loss, metric=mae) + ) + torch.save(checkpoint, save_path) + +if __name__ == "__main__": + trainer = BrainAgeTrainer() + trainer.train() \ No newline at end of file diff --git a/src/BrainIAC/HD_BET/__pycache__/config.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd5fefbfdd6c33ded5680391725b1501da66b8d6 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/config.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/config.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/config.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61cf361fe68cd40f9c85c2fb100d27c9d1cfb4c0 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/config.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/config.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0804f212002980f7ee1b2de3b9a49d5d7b34e05 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/config.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84c28b095603940c02cc2ea9fa91b0b0446c10eb Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04949544425bca1aba1f446b3fdb054012d51adc Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c87337e83582ef7e61d9c7a027040f52afef166 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/data_loading.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffe26540ea77664aa42c19aee6a76b18156a53d5 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81aa4cfb5eb39f270424b98107c40d28744386eb Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/hd_bet.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e496ed582831f96a58feb9e3865c4081ab9d11db Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c20f2e3c729378c589e22eff4e8ec151d20b470 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5516dcf581d4e55ef49ee14ff2dbac3860f0086 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/network_architecture.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/paths.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac20a90e2d0069f0ca86e7dabb67a0baa1e93440 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/paths.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..567f623c1fa55d81bc9c1d4001d6aea1ab274f5f Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/paths.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a2228880835eb9a907395309369a846da5010f2 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/paths.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3511e128de506944ecb85ca4fed03b31b5531dc Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a8f633c24280649275fb7bb7182991c07ced8eb Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..294527e04fcfd5d3be038dc1c8712e925d3352ff Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/predict_case.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/run.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/run.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0cce40e0b34a33a6d930014fc91d903d4892fb5 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/run.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/run.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/run.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5747d903a2b450fbeb16df2ec1f1d3680fcc6a48 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/run.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/run.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/run.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51e286390fe2b53d201a211aee37bf7b952fcfc3 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/run.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/utils.cpython-310.pyc b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4388f005b876e63a702375b68a13143344f4dff9 Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-310.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/utils.cpython-38.pyc b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad8b6ff21be7f57044f1a5e0f76501545f0fb97d Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-38.pyc differ diff --git a/src/BrainIAC/HD_BET/__pycache__/utils.cpython-39.pyc b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe6e8221daae04ee32f9159e87904a914887135d Binary files /dev/null and b/src/BrainIAC/HD_BET/__pycache__/utils.cpython-39.pyc differ diff --git a/src/BrainIAC/HD_BET/config.py b/src/BrainIAC/HD_BET/config.py new file mode 100644 index 0000000000000000000000000000000000000000..870951e5c9059fb9e20d6143e68266732f19234e --- /dev/null +++ b/src/BrainIAC/HD_BET/config.py @@ -0,0 +1,121 @@ +import numpy as np +import torch +from HD_BET.utils import SetNetworkToVal, softmax_helper +from abc import abstractmethod +from HD_BET.network_architecture import Network + + +class BaseConfig(object): + def __init__(self): + pass + + @abstractmethod + def get_split(self, fold, random_state=12345): + pass + + @abstractmethod + def get_network(self, mode="train"): + pass + + @abstractmethod + def get_basic_generators(self, fold): + pass + + @abstractmethod + def get_data_generators(self, fold): + pass + + def preprocess(self, data): + return data + + def __repr__(self): + res = "" + for v in vars(self): + if not v.startswith("__") and not v.startswith("_") and v != 'dataset': + res += (v + ": " + str(self.__getattribute__(v)) + "\n") + return res + + +class HD_BET_Config(BaseConfig): + def __init__(self): + super(HD_BET_Config, self).__init__() + + self.EXPERIMENT_NAME = self.__class__.__name__ # just a generic experiment name + + # network parameters + self.net_base_num_layers = 21 + self.BATCH_SIZE = 2 + self.net_do_DS = True + self.net_dropout_p = 0.0 + self.net_use_inst_norm = True + self.net_conv_use_bias = True + self.net_norm_use_affine = True + self.net_leaky_relu_slope = 1e-1 + + # hyperparameters + self.INPUT_PATCH_SIZE = (128, 128, 128) + self.num_classes = 2 + self.selected_data_channels = range(1) + + # data augmentation + self.da_mirror_axes = (2, 3, 4) + + # validation + self.val_use_DO = False + self.val_use_train_mode = False # for dropout sampling + self.val_num_repeats = 1 # only useful if dropout sampling + self.val_batch_size = 1 # only useful if dropout sampling + self.val_save_npz = True + self.val_do_mirroring = True # test time data augmentation via mirroring + self.val_write_images = True + self.net_input_must_be_divisible_by = 16 # we could make a network class that has this as a property + self.val_min_size = self.INPUT_PATCH_SIZE + self.val_fn = None + + # CAREFUL! THIS IS A HACK TO MAKE PYTORCH 0.3 STATE DICTS COMPATIBLE WITH PYTORCH 0.4 (setting keep_runnings_ + # stats=True but not using them in validation. keep_runnings_stats was True before 0.3 but unused and defaults + # to false in 0.4) + self.val_use_moving_averages = False + + def get_network(self, train=True, pretrained_weights=None): + net = Network(self.num_classes, len(self.selected_data_channels), self.net_base_num_layers, + self.net_dropout_p, softmax_helper, self.net_leaky_relu_slope, self.net_conv_use_bias, + self.net_norm_use_affine, True, self.net_do_DS) + + if pretrained_weights is not None: + net.load_state_dict( + torch.load(pretrained_weights, map_location=lambda storage, loc: storage)) + + if train: + net.train(True) + else: + net.train(False) + net.apply(SetNetworkToVal(self.val_use_DO, self.val_use_moving_averages)) + net.do_ds = False + + optimizer = None + self.lr_scheduler = None + return net, optimizer + + def get_data_generators(self, fold): + pass + + def get_split(self, fold, random_state=12345): + pass + + def get_basic_generators(self, fold): + pass + + def on_epoch_end(self, epoch): + pass + + def preprocess(self, data): + data = np.copy(data) + for c in range(data.shape[0]): + data[c] -= data[c].mean() + data[c] /= data[c].std() + return data + + +config = HD_BET_Config + diff --git a/src/BrainIAC/HD_BET/data_loading.py b/src/BrainIAC/HD_BET/data_loading.py new file mode 100644 index 0000000000000000000000000000000000000000..8ec4be63a8186b65bfb390770fefa6217b5dd2c5 --- /dev/null +++ b/src/BrainIAC/HD_BET/data_loading.py @@ -0,0 +1,121 @@ +import SimpleITK as sitk +import numpy as np +from skimage.transform import resize + + +def resize_image(image, old_spacing, new_spacing, order=3): + new_shape = (int(np.round(old_spacing[0]/new_spacing[0]*float(image.shape[0]))), + int(np.round(old_spacing[1]/new_spacing[1]*float(image.shape[1]))), + int(np.round(old_spacing[2]/new_spacing[2]*float(image.shape[2])))) + return resize(image, new_shape, order=order, mode='edge', cval=0, anti_aliasing=False) + + +def preprocess_image(itk_image, is_seg=False, spacing_target=(1, 0.5, 0.5)): + spacing = np.array(itk_image.GetSpacing())[[2, 1, 0]] + image = sitk.GetArrayFromImage(itk_image).astype(float) + + assert len(image.shape) == 3, "The image has unsupported number of dimensions. Only 3D images are allowed" + + if not is_seg: + if np.any([[i != j] for i, j in zip(spacing, spacing_target)]): + image = resize_image(image, spacing, spacing_target).astype(np.float32) + + image -= image.mean() + image /= image.std() + else: + new_shape = (int(np.round(spacing[0] / spacing_target[0] * float(image.shape[0]))), + int(np.round(spacing[1] / spacing_target[1] * float(image.shape[1]))), + int(np.round(spacing[2] / spacing_target[2] * float(image.shape[2])))) + image = resize_segmentation(image, new_shape, 1) + return image + + +def load_and_preprocess(mri_file): + images = {} + # t1 + images["T1"] = sitk.ReadImage(mri_file) + + properties_dict = { + "spacing": images["T1"].GetSpacing(), + "direction": images["T1"].GetDirection(), + "size": images["T1"].GetSize(), + "origin": images["T1"].GetOrigin() + } + + for k in images.keys(): + images[k] = preprocess_image(images[k], is_seg=False, spacing_target=(1.5, 1.5, 1.5)) + + properties_dict['size_before_cropping'] = images["T1"].shape + + imgs = [] + for seq in ['T1']: + imgs.append(images[seq][None]) + all_data = np.vstack(imgs) + print("image shape after preprocessing: ", str(all_data[0].shape)) + return all_data, properties_dict + + +def save_segmentation_nifti(segmentation, dct, out_fname, order=1): + ''' + segmentation must have the same spacing as the original nifti (for now). segmentation may have been cropped out + of the original image + + dct: + size_before_cropping + brain_bbox + size -> this is the original size of the dataset, if the image was not resampled, this is the same as size_before_cropping + spacing + origin + direction + + :param segmentation: + :param dct: + :param out_fname: + :return: + ''' + old_size = dct.get('size_before_cropping') + bbox = dct.get('brain_bbox') + if bbox is not None: + seg_old_size = np.zeros(old_size) + for c in range(3): + bbox[c][1] = np.min((bbox[c][0] + segmentation.shape[c], old_size[c])) + seg_old_size[bbox[0][0]:bbox[0][1], + bbox[1][0]:bbox[1][1], + bbox[2][0]:bbox[2][1]] = segmentation + else: + seg_old_size = segmentation + if np.any(np.array(seg_old_size) != np.array(dct['size'])[[2, 1, 0]]): + seg_old_spacing = resize_segmentation(seg_old_size, np.array(dct['size'])[[2, 1, 0]], order=order) + else: + seg_old_spacing = seg_old_size + seg_resized_itk = sitk.GetImageFromArray(seg_old_spacing.astype(np.int32)) + seg_resized_itk.SetSpacing(np.array(dct['spacing'])[[0, 1, 2]]) + seg_resized_itk.SetOrigin(dct['origin']) + seg_resized_itk.SetDirection(dct['direction']) + sitk.WriteImage(seg_resized_itk, out_fname) + + +def resize_segmentation(segmentation, new_shape, order=3, cval=0): + ''' + Taken from batchgenerators (https://github.com/MIC-DKFZ/batchgenerators) to prevent dependency + + Resizes a segmentation map. Supports all orders (see skimage documentation). Will transform segmentation map to one + hot encoding which is resized and transformed back to a segmentation map. + This prevents interpolation artifacts ([0, 0, 2] -> [0, 1, 2]) + :param segmentation: + :param new_shape: + :param order: + :return: + ''' + tpe = segmentation.dtype + unique_labels = np.unique(segmentation) + assert len(segmentation.shape) == len(new_shape), "new shape must have same dimensionality as segmentation" + if order == 0: + return resize(segmentation, new_shape, order, mode="constant", cval=cval, clip=True, anti_aliasing=False).astype(tpe) + else: + reshaped = np.zeros(new_shape, dtype=segmentation.dtype) + + for i, c in enumerate(unique_labels): + reshaped_multihot = resize((segmentation == c).astype(float), new_shape, order, mode="edge", clip=True, anti_aliasing=False) + reshaped[reshaped_multihot >= 0.5] = c + return reshaped diff --git a/src/BrainIAC/HD_BET/hd_bet.py b/src/BrainIAC/HD_BET/hd_bet.py new file mode 100644 index 0000000000000000000000000000000000000000..128575b6cfb4bdd98bf417ed598f905ef4896fd1 --- /dev/null +++ b/src/BrainIAC/HD_BET/hd_bet.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.append("/mnt/93E8-0534/AIDAN/HDBET/") +from HD_BET.run import run_hd_bet +from HD_BET.utils import maybe_mkdir_p, subfiles +import HD_BET + +def hd_bet(input_file_or_dir,output_file_or_dir,mode,device,tta,pp=1,save_mask=0,overwrite_existing=1): + + if output_file_or_dir is None: + output_file_or_dir = os.path.join(os.path.dirname(input_file_or_dir), + os.path.basename(input_file_or_dir).split(".")[0] + "_bet") + + + params_file = os.path.join(HD_BET.__path__[0], "model_final.py") + config_file = os.path.join(HD_BET.__path__[0], "config.py") + + assert os.path.abspath(input_file_or_dir) != os.path.abspath(output_file_or_dir), "output must be different from input" + + if device == 'cpu': + pass + else: + device = int(device) + + if os.path.isdir(input_file_or_dir): + maybe_mkdir_p(output_file_or_dir) + input_files = subfiles(input_file_or_dir, suffix='_0000.nii.gz', join=False) + + if len(input_files) == 0: + raise RuntimeError("input is a folder but no nifti files (.nii.gz) were found in here") + + output_files = [os.path.join(output_file_or_dir, i) for i in input_files] + input_files = [os.path.join(input_file_or_dir, i) for i in input_files] + else: + if not output_file_or_dir.endswith('.nii.gz'): + output_file_or_dir += '.nii.gz' + assert os.path.abspath(input_file_or_dir) != os.path.abspath(output_file_or_dir), "output must be different from input" + + output_files = [output_file_or_dir] + input_files = [input_file_or_dir] + + if tta == 0: + tta = False + elif tta == 1: + tta = True + else: + raise ValueError("Unknown value for tta: %s. Expected: 0 or 1" % str(tta)) + + if overwrite_existing == 0: + overwrite_existing = False + elif overwrite_existing == 1: + overwrite_existing = True + else: + raise ValueError("Unknown value for overwrite_existing: %s. Expected: 0 or 1" % str(overwrite_existing)) + + if pp == 0: + pp = False + elif pp == 1: + pp = True + else: + raise ValueError("Unknown value for pp: %s. Expected: 0 or 1" % str(pp)) + + if save_mask == 0: + save_mask = False + elif save_mask == 1: + save_mask = True + else: + raise ValueError("Unknown value for pp: %s. Expected: 0 or 1" % str(pp)) + + run_hd_bet(input_files, output_files, mode, config_file, device, pp, tta, save_mask, overwrite_existing) + + +if __name__ == "__main__": + print("\n########################") + print("If you are using hd-bet, please cite the following paper:") + print("Isensee F, Schell M, Tursunova I, Brugnara G, Bonekamp D, Neuberger U, Wick A, Schlemmer HP, Heiland S, Wick W," + "Bendszus M, Maier-Hein KH, Kickingereder P. Automated brain extraction of multi-sequence MRI using artificial" + "neural networks. arXiv preprint arXiv:1901.11341, 2019.") + print("########################\n") + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--input', help='input. Can be either a single file name or an input folder. If file: must be ' + 'nifti (.nii.gz) and can only be 3D. No support for 4d images, use fslsplit to ' + 'split 4d sequences into 3d images. If folder: all files ending with .nii.gz ' + 'within that folder will be brain extracted.', required=True, type=str) + parser.add_argument('-o', '--output', help='output. Can be either a filename or a folder. If it does not exist, the folder' + ' will be created', required=False, type=str) + parser.add_argument('-mode', type=str, default='accurate', help='can be either \'fast\' or \'accurate\'. Fast will ' + 'use only one set of parameters whereas accurate will ' + 'use the five sets of parameters that resulted from ' + 'our cross-validation as an ensemble. Default: ' + 'accurate', + required=False) + parser.add_argument('-device', default='0', type=str, help='used to set on which device the prediction will run. ' + 'Must be either int or str. Use int for GPU id or ' + '\'cpu\' to run on CPU. When using CPU you should ' + 'consider disabling tta. Default for -device is: 0', + required=False) + parser.add_argument('-tta', default=1, required=False, type=int, help='whether to use test time data augmentation ' + '(mirroring). 1= True, 0=False. Disable this ' + 'if you are using CPU to speed things up! ' + 'Default: 1') + parser.add_argument('-pp', default=1, type=int, required=False, help='set to 0 to disabe postprocessing (remove all' + ' but the largest connected component in ' + 'the prediction. Default: 1') + parser.add_argument('-s', '--save_mask', default=1, type=int, required=False, help='if set to 0 the segmentation ' + 'mask will not be ' + 'saved') + parser.add_argument('--overwrite_existing', default=1, type=int, required=False, help="set this to 0 if you don't " + "want to overwrite existing " + "predictions") + + args = parser.parse_args() + + hd_bet(args.input,args.output,args.mode,args.device,args.tta,args.pp,args.save_mask,args.overwrite_existing) + diff --git a/src/BrainIAC/HD_BET/network_architecture.py b/src/BrainIAC/HD_BET/network_architecture.py new file mode 100644 index 0000000000000000000000000000000000000000..0824aa10839024368ad8ab38c637ce81aa9327e5 --- /dev/null +++ b/src/BrainIAC/HD_BET/network_architecture.py @@ -0,0 +1,213 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from HD_BET.utils import softmax_helper + + +class EncodingModule(nn.Module): + def __init__(self, in_channels, out_channels, filter_size=3, dropout_p=0.3, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True): + nn.Module.__init__(self) + self.dropout_p = dropout_p + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.bn_1 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv1 = nn.Conv3d(in_channels, out_channels, filter_size, 1, (filter_size - 1) // 2, bias=self.conv_bias) + self.dropout = nn.Dropout3d(dropout_p) + self.bn_2 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv2 = nn.Conv3d(out_channels, out_channels, filter_size, 1, (filter_size - 1) // 2, bias=self.conv_bias) + + def forward(self, x): + skip = x + x = F.leaky_relu(self.bn_1(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.conv1(x) + if self.dropout_p is not None and self.dropout_p > 0: + x = self.dropout(x) + x = F.leaky_relu(self.bn_2(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.conv2(x) + x = x + skip + return x + + +class Upsample(nn.Module): + def __init__(self, size=None, scale_factor=None, mode='nearest', align_corners=True): + super(Upsample, self).__init__() + self.align_corners = align_corners + self.mode = mode + self.scale_factor = scale_factor + self.size = size + + def forward(self, x): + return nn.functional.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode, + align_corners=self.align_corners) + + +class LocalizationModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.conv1 = nn.Conv3d(in_channels, in_channels, 3, 1, 1, bias=self.conv_bias) + self.bn_1 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv2 = nn.Conv3d(in_channels, out_channels, 1, 1, 0, bias=self.conv_bias) + self.bn_2 = nn.InstanceNorm3d(out_channels, affine=self.inst_norm_affine, track_running_stats=True) + + def forward(self, x): + x = F.leaky_relu(self.bn_1(self.conv1(x)), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = F.leaky_relu(self.bn_2(self.conv2(x)), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + return x + + +class UpsamplingModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.upsample = Upsample(scale_factor=2, mode="trilinear", align_corners=True) + self.upsample_conv = nn.Conv3d(in_channels, out_channels, 3, 1, 1, bias=self.conv_bias) + self.bn = nn.InstanceNorm3d(out_channels, affine=self.inst_norm_affine, track_running_stats=True) + + def forward(self, x): + x = F.leaky_relu(self.bn(self.upsample_conv(self.upsample(x))), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + return x + + +class DownsamplingModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.bn = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.downsample = nn.Conv3d(in_channels, out_channels, 3, 2, 1, bias=self.conv_bias) + + def forward(self, x): + x = F.leaky_relu(self.bn(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + b = self.downsample(x) + return x, b + + +class Network(nn.Module): + def __init__(self, num_classes=4, num_input_channels=4, base_filters=16, dropout_p=0.3, + final_nonlin=softmax_helper, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True, do_ds=True): + super(Network, self).__init__() + + self.do_ds = do_ds + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.final_nonlin = final_nonlin + self.init_conv = nn.Conv3d(num_input_channels, base_filters, 3, 1, 1, bias=self.conv_bias) + + self.context1 = EncodingModule(base_filters, base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down1 = DownsamplingModule(base_filters, base_filters * 2, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context2 = EncodingModule(2 * base_filters, 2 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down2 = DownsamplingModule(2 * base_filters, base_filters * 4, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context3 = EncodingModule(4 * base_filters, 4 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down3 = DownsamplingModule(4 * base_filters, base_filters * 8, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context4 = EncodingModule(8 * base_filters, 8 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down4 = DownsamplingModule(8 * base_filters, base_filters * 16, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context5 = EncodingModule(16 * base_filters, 16 * base_filters, 3, dropout_p, leakiness=1e-2, + conv_bias=True, inst_norm_affine=True, lrelu_inplace=True) + + self.bn_after_context5 = nn.InstanceNorm3d(16 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.up1 = UpsamplingModule(16 * base_filters, 8 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc1 = LocalizationModule(16 * base_filters, 8 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.up2 = UpsamplingModule(8 * base_filters, 4 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc2 = LocalizationModule(8 * base_filters, 4 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.loc2_seg = nn.Conv3d(4 * base_filters, num_classes, 1, 1, 0, bias=False) + self.up3 = UpsamplingModule(4 * base_filters, 2 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc3 = LocalizationModule(4 * base_filters, 2 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.loc3_seg = nn.Conv3d(2 * base_filters, num_classes, 1, 1, 0, bias=False) + self.up4 = UpsamplingModule(2 * base_filters, 1 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.end_conv_1 = nn.Conv3d(2 * base_filters, 2 * base_filters, 3, 1, 1, bias=self.conv_bias) + self.end_conv_1_bn = nn.InstanceNorm3d(2 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.end_conv_2 = nn.Conv3d(2 * base_filters, 2 * base_filters, 3, 1, 1, bias=self.conv_bias) + self.end_conv_2_bn = nn.InstanceNorm3d(2 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.seg_layer = nn.Conv3d(2 * base_filters, num_classes, 1, 1, 0, bias=False) + + def forward(self, x): + seg_outputs = [] + + x = self.init_conv(x) + x = self.context1(x) + + skip1, x = self.down1(x) + x = self.context2(x) + + skip2, x = self.down2(x) + x = self.context3(x) + + skip3, x = self.down3(x) + x = self.context4(x) + + skip4, x = self.down4(x) + x = self.context5(x) + + x = F.leaky_relu(self.bn_after_context5(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.up1(x) + + x = torch.cat((skip4, x), dim=1) + x = self.loc1(x) + x = self.up2(x) + + x = torch.cat((skip3, x), dim=1) + x = self.loc2(x) + loc2_seg = self.final_nonlin(self.loc2_seg(x)) + seg_outputs.append(loc2_seg) + x = self.up3(x) + + x = torch.cat((skip2, x), dim=1) + x = self.loc3(x) + loc3_seg = self.final_nonlin(self.loc3_seg(x)) + seg_outputs.append(loc3_seg) + x = self.up4(x) + + x = torch.cat((skip1, x), dim=1) + x = F.leaky_relu(self.end_conv_1_bn(self.end_conv_1(x)), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + x = F.leaky_relu(self.end_conv_2_bn(self.end_conv_2(x)), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + x = self.final_nonlin(self.seg_layer(x)) + seg_outputs.append(x) + + if self.do_ds: + return seg_outputs[::-1] + else: + return seg_outputs[-1] diff --git a/src/BrainIAC/HD_BET/paths.py b/src/BrainIAC/HD_BET/paths.py new file mode 100644 index 0000000000000000000000000000000000000000..b8bcb1c114e504a6f2e3b2be41695fc0a3ed9a8c --- /dev/null +++ b/src/BrainIAC/HD_BET/paths.py @@ -0,0 +1,6 @@ +import os + +# please refer to the readme on where to get the parameters. Save them in this folder: +# Original Path: "/media/sdb/divyanshu/divyanshu/aidan_segmentation/nnUNet_pLGG/home/divyanshu/hd-bet_params" +# Updated path for Docker container: +folder_with_parameter_files = "/app/BrainIAC/hdbet_model" diff --git a/src/BrainIAC/HD_BET/predict_case.py b/src/BrainIAC/HD_BET/predict_case.py new file mode 100644 index 0000000000000000000000000000000000000000..559c66739ae890f7e985e072eb49ce0ee0484978 --- /dev/null +++ b/src/BrainIAC/HD_BET/predict_case.py @@ -0,0 +1,126 @@ +import torch +import numpy as np + + +def pad_patient_3D(patient, shape_must_be_divisible_by=16, min_size=None): + if not (isinstance(shape_must_be_divisible_by, list) or isinstance(shape_must_be_divisible_by, tuple)): + shape_must_be_divisible_by = [shape_must_be_divisible_by] * 3 + shp = patient.shape + new_shp = [shp[0] + shape_must_be_divisible_by[0] - shp[0] % shape_must_be_divisible_by[0], + shp[1] + shape_must_be_divisible_by[1] - shp[1] % shape_must_be_divisible_by[1], + shp[2] + shape_must_be_divisible_by[2] - shp[2] % shape_must_be_divisible_by[2]] + for i in range(len(shp)): + if shp[i] % shape_must_be_divisible_by[i] == 0: + new_shp[i] -= shape_must_be_divisible_by[i] + if min_size is not None: + new_shp = np.max(np.vstack((np.array(new_shp), np.array(min_size))), 0) + return reshape_by_padding_upper_coords(patient, new_shp, 0), shp + + +def reshape_by_padding_upper_coords(image, new_shape, pad_value=None): + shape = tuple(list(image.shape)) + new_shape = tuple(np.max(np.concatenate((shape, new_shape)).reshape((2,len(shape))), axis=0)) + if pad_value is None: + if len(shape) == 2: + pad_value = image[0,0] + elif len(shape) == 3: + pad_value = image[0, 0, 0] + else: + raise ValueError("Image must be either 2 or 3 dimensional") + res = np.ones(list(new_shape), dtype=image.dtype) * pad_value + if len(shape) == 2: + res[0:0+int(shape[0]), 0:0+int(shape[1])] = image + elif len(shape) == 3: + res[0:0+int(shape[0]), 0:0+int(shape[1]), 0:0+int(shape[2])] = image + return res + + +def predict_case_3D_net(net, patient_data, do_mirroring, num_repeats, BATCH_SIZE=None, + new_shape_must_be_divisible_by=16, min_size=None, main_device=0, mirror_axes=(2, 3, 4)): + with torch.no_grad(): + pad_res = [] + for i in range(patient_data.shape[0]): + t, old_shape = pad_patient_3D(patient_data[i], new_shape_must_be_divisible_by, min_size) + pad_res.append(t[None]) + + patient_data = np.vstack(pad_res) + + new_shp = patient_data.shape + + data = np.zeros(tuple([1] + list(new_shp)), dtype=np.float32) + + data[0] = patient_data + + if BATCH_SIZE is not None: + data = np.vstack([data] * BATCH_SIZE) + + a = torch.rand(data.shape).float() + + if main_device == 'cpu': + pass + else: + a = a.cuda(main_device) + + if do_mirroring: + x = 8 + else: + x = 1 + all_preds = [] + for i in range(num_repeats): + for m in range(x): + data_for_net = np.array(data) + do_stuff = False + if m == 0: + do_stuff = True + pass + if m == 1 and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, :, ::-1] + if m == 2 and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, ::-1, :] + if m == 3 and (4 in mirror_axes) and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, ::-1, ::-1] + if m == 4 and (2 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, :, :] + if m == 5 and (2 in mirror_axes) and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, :, ::-1] + if m == 6 and (2 in mirror_axes) and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, ::-1, :] + if m == 7 and (2 in mirror_axes) and (3 in mirror_axes) and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, ::-1, ::-1] + + if do_stuff: + _ = a.data.copy_(torch.from_numpy(np.copy(data_for_net))) + p = net(a) # np.copy is necessary because ::-1 creates just a view i think + p = p.data.cpu().numpy() + + if m == 0: + pass + if m == 1 and (4 in mirror_axes): + p = p[:, :, :, :, ::-1] + if m == 2 and (3 in mirror_axes): + p = p[:, :, :, ::-1, :] + if m == 3 and (4 in mirror_axes) and (3 in mirror_axes): + p = p[:, :, :, ::-1, ::-1] + if m == 4 and (2 in mirror_axes): + p = p[:, :, ::-1, :, :] + if m == 5 and (2 in mirror_axes) and (4 in mirror_axes): + p = p[:, :, ::-1, :, ::-1] + if m == 6 and (2 in mirror_axes) and (3 in mirror_axes): + p = p[:, :, ::-1, ::-1, :] + if m == 7 and (2 in mirror_axes) and (3 in mirror_axes) and (4 in mirror_axes): + p = p[:, :, ::-1, ::-1, ::-1] + all_preds.append(p) + + stacked = np.vstack(all_preds)[:, :, :old_shape[0], :old_shape[1], :old_shape[2]] + predicted_segmentation = stacked.mean(0).argmax(0) + uncertainty = stacked.var(0) + bayesian_predictions = stacked + softmax_pred = stacked.mean(0) + return predicted_segmentation, bayesian_predictions, softmax_pred, uncertainty diff --git a/src/BrainIAC/HD_BET/run.py b/src/BrainIAC/HD_BET/run.py new file mode 100644 index 0000000000000000000000000000000000000000..858934d8f67175df508884e9030f8d38ba0d07cf --- /dev/null +++ b/src/BrainIAC/HD_BET/run.py @@ -0,0 +1,117 @@ +import torch +import numpy as np +import SimpleITK as sitk +from HD_BET.data_loading import load_and_preprocess, save_segmentation_nifti +from HD_BET.predict_case import predict_case_3D_net +import imp +from HD_BET.utils import postprocess_prediction, SetNetworkToVal, get_params_fname, maybe_download_parameters +import os +import HD_BET + + +def apply_bet(img, bet, out_fname): + img_itk = sitk.ReadImage(img) + img_npy = sitk.GetArrayFromImage(img_itk) + img_bet = sitk.GetArrayFromImage(sitk.ReadImage(bet)) + img_npy[img_bet == 0] = 0 + out = sitk.GetImageFromArray(img_npy) + out.CopyInformation(img_itk) + sitk.WriteImage(out, out_fname) + + +def run_hd_bet(mri_fnames, output_fnames, mode="accurate", config_file=os.path.join(HD_BET.__path__[0], "config.py"), device=0, + postprocess=False, do_tta=True, keep_mask=True, overwrite=True): + """ + + :param mri_fnames: str or list/tuple of str + :param output_fnames: str or list/tuple of str. If list: must have the same length as output_fnames + :param mode: fast or accurate + :param config_file: config.py + :param device: either int (for device id) or 'cpu' + :param postprocess: whether to do postprocessing or not. Postprocessing here consists of simply discarding all + but the largest predicted connected component. Default False + :param do_tta: whether to do test time data augmentation by mirroring along all axes. Default: True. If you use + CPU you may want to turn that off to speed things up + :return: + """ + + list_of_param_files = [] + + if mode == 'fast': + params_file = get_params_fname(0) + maybe_download_parameters(0) + + list_of_param_files.append(params_file) + elif mode == 'accurate': + for i in range(5): + params_file = get_params_fname(i) + maybe_download_parameters(i) + + list_of_param_files.append(params_file) + else: + raise ValueError("Unknown value for mode: %s. Expected: fast or accurate" % mode) + + assert all([os.path.isfile(i) for i in list_of_param_files]), "Could not find parameter files" + + cf = imp.load_source('cf', config_file) + cf = cf.config() + + net, _ = cf.get_network(cf.val_use_train_mode, None) + if device == "cpu": + net = net.cpu() + else: + net.cuda(device) + + if not isinstance(mri_fnames, (list, tuple)): + mri_fnames = [mri_fnames] + + if not isinstance(output_fnames, (list, tuple)): + output_fnames = [output_fnames] + + assert len(mri_fnames) == len(output_fnames), "mri_fnames and output_fnames must have the same length" + + params = [] + for p in list_of_param_files: + params.append(torch.load(p, map_location=lambda storage, loc: storage)) + + for in_fname, out_fname in zip(mri_fnames, output_fnames): + mask_fname = out_fname[:-7] + "_mask.nii.gz" + if overwrite or (not (os.path.isfile(mask_fname) and keep_mask) or not os.path.isfile(out_fname)): + print("File:", in_fname) + print("preprocessing...") + try: + data, data_dict = load_and_preprocess(in_fname) + except RuntimeError: + print("\nERROR\nCould not read file", in_fname, "\n") + continue + except AssertionError as e: + print(e) + continue + + softmax_preds = [] + + print("prediction (CNN id)...") + for i, p in enumerate(params): + print(i) + net.load_state_dict(p) + net.eval() + net.apply(SetNetworkToVal(False, False)) + _, _, softmax_pred, _ = predict_case_3D_net(net, data, do_tta, cf.val_num_repeats, + cf.val_batch_size, cf.net_input_must_be_divisible_by, + cf.val_min_size, device, cf.da_mirror_axes) + softmax_preds.append(softmax_pred[None]) + + seg = np.argmax(np.vstack(softmax_preds).mean(0), 0) + + if postprocess: + seg = postprocess_prediction(seg) + + print("exporting segmentation...") + save_segmentation_nifti(seg, data_dict, mask_fname) + + apply_bet(in_fname, mask_fname, out_fname) + + if not keep_mask: + os.remove(mask_fname) + + diff --git a/src/BrainIAC/HD_BET/utils.py b/src/BrainIAC/HD_BET/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..3ba72a3d4d70accfd1fdc313a2f80b8d4c4c6eea --- /dev/null +++ b/src/BrainIAC/HD_BET/utils.py @@ -0,0 +1,115 @@ +from urllib.request import urlopen +import torch +from torch import nn +import numpy as np +from skimage.morphology import label +import os +from HD_BET.paths import folder_with_parameter_files + + +def get_params_fname(fold): + return os.path.join(folder_with_parameter_files, "%d.model" % fold) + + +def maybe_download_parameters(fold=0, force_overwrite=False): + """ + Downloads the parameters for some fold if it is not present yet. + :param fold: + :param force_overwrite: if True the old parameter file will be deleted (if present) prior to download + :return: + """ + + assert 0 <= fold <= 4, "fold must be between 0 and 4" + + if not os.path.isdir(folder_with_parameter_files): + maybe_mkdir_p(folder_with_parameter_files) + + out_filename = get_params_fname(fold) + + if force_overwrite and os.path.isfile(out_filename): + os.remove(out_filename) + + if not os.path.isfile(out_filename): + url = "https://zenodo.org/record/2540695/files/%d.model?download=1" % fold + print("Downloading", url, "...") + data = urlopen(url).read() + #out_filename = "/media/sdb/divyanshu/divyanshu/aidan_segmentation/nnUNet_pLGG/home/divyanshu/hd-bet_params/0.model" + with open(out_filename, 'wb') as f: + f.write(data) + + +def init_weights(module): + if isinstance(module, nn.Conv3d): + module.weight = nn.init.kaiming_normal(module.weight, a=1e-2) + if module.bias is not None: + module.bias = nn.init.constant(module.bias, 0) + + +def softmax_helper(x): + rpt = [1 for _ in range(len(x.size()))] + rpt[1] = x.size(1) + x_max = x.max(1, keepdim=True)[0].repeat(*rpt) + e_x = torch.exp(x - x_max) + return e_x / e_x.sum(1, keepdim=True).repeat(*rpt) + + +class SetNetworkToVal(object): + def __init__(self, use_dropout_sampling=False, norm_use_average=True): + self.norm_use_average = norm_use_average + self.use_dropout_sampling = use_dropout_sampling + + def __call__(self, module): + if isinstance(module, nn.Dropout3d) or isinstance(module, nn.Dropout2d) or isinstance(module, nn.Dropout): + module.train(self.use_dropout_sampling) + elif isinstance(module, nn.InstanceNorm3d) or isinstance(module, nn.InstanceNorm2d) or \ + isinstance(module, nn.InstanceNorm1d) \ + or isinstance(module, nn.BatchNorm2d) or isinstance(module, nn.BatchNorm3d) or \ + isinstance(module, nn.BatchNorm1d): + module.train(not self.norm_use_average) + + +def postprocess_prediction(seg): + # basically look for connected components and choose the largest one, delete everything else + print("running postprocessing... ") + mask = seg != 0 + lbls = label(mask, connectivity=mask.ndim) + lbls_sizes = [np.sum(lbls == i) for i in np.unique(lbls)] + largest_region = np.argmax(lbls_sizes[1:]) + 1 + seg[lbls != largest_region] = 0 + return seg + + +def subdirs(folder, join=True, prefix=None, suffix=None, sort=True): + if join: + l = os.path.join + else: + l = lambda x, y: y + res = [l(folder, i) for i in os.listdir(folder) if os.path.isdir(os.path.join(folder, i)) + and (prefix is None or i.startswith(prefix)) + and (suffix is None or i.endswith(suffix))] + if sort: + res.sort() + return res + + +def subfiles(folder, join=True, prefix=None, suffix=None, sort=True): + if join: + l = os.path.join + else: + l = lambda x, y: y + res = [l(folder, i) for i in os.listdir(folder) if os.path.isfile(os.path.join(folder, i)) + and (prefix is None or i.startswith(prefix)) + and (suffix is None or i.endswith(suffix))] + if sort: + res.sort() + return res + + +subfolders = subdirs # I am tired of confusing those + + +def maybe_mkdir_p(directory): + splits = directory.split("/")[1:] + for i in range(0, len(splits)): + if not os.path.isdir(os.path.join("", *splits[:i+1])): + os.mkdir(os.path.join("", *splits[:i+1])) diff --git a/src/BrainIAC/MCIclassification/README.md b/src/BrainIAC/MCIclassification/README.md new file mode 100644 index 0000000000000000000000000000000000000000..670a96fdaa26e24ef814b3af5dfd9bdcefff5a86 --- /dev/null +++ b/src/BrainIAC/MCIclassification/README.md @@ -0,0 +1,52 @@ +# MCI Classification + +

+ MCI Classification Example +

+ +## Overview + +We present the MCI classification training and inference code for BrainIAC as a downstream task. The pipeline is trained and infered on T1 scans, with AUC and F1 as evaluation metric. + +## Data Requirements + +- **Input**: T1-weighted MR scans +- **Format**: NIFTI (.nii.gz) +- **Preprocessing**: Bias field corrected, registered to standard space, skull stripped, histogram normalized (optional) +- **CSV Structure**: + ``` + pat_id,scandate,label + subject001,20240101,1 # 1 for MCI, 0 for healthy control + ``` +refer to [ quickstart.ipynb](../quickstart.ipynb) to find how to preprocess data and generate csv file. + +## Setup + +1. **Configuration**: +change the [config.yml](../config.yml) file accordingly. + ```yaml + # config.yml + data: + train_csv: "path/to/train.csv" + val_csv: "path/to/val.csv" + test_csv: "path/to/test.csv" + root_dir: "../data/sample/processed" + collate: 1 # single scan framework + + checkpoints: "./checkpoints/mci_model.00" # for inference/testing + + train: + finetune: 'yes' # yes to finetune the entire model + freeze: 'no' # yes to freeze the resnet backbone + weights: ./checkpoints/brainiac.ckpt # path to brainiac weights + ``` + +2. **Training**: + ```bash + python -m MCIclassification.train_mci + ``` + +3. **Inference**: + ```bash + python -m MCIclassification.infer_mci + ``` diff --git a/src/BrainIAC/MCIclassification/__init__.py b/src/BrainIAC/MCIclassification/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/MCIclassification/__pycache__/__init__.cpython-39.pyc b/src/BrainIAC/MCIclassification/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7570da64ee90adfbdc21d56ac65907a9f18a4e3e Binary files /dev/null and b/src/BrainIAC/MCIclassification/__pycache__/__init__.cpython-39.pyc differ diff --git a/src/BrainIAC/MCIclassification/__pycache__/infer_mci.cpython-39.pyc b/src/BrainIAC/MCIclassification/__pycache__/infer_mci.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24bcc8af7186f17123cc0f738034fa9c4fa2da40 Binary files /dev/null and b/src/BrainIAC/MCIclassification/__pycache__/infer_mci.cpython-39.pyc differ diff --git a/src/BrainIAC/MCIclassification/infer_mci.py b/src/BrainIAC/MCIclassification/infer_mci.py new file mode 100644 index 0000000000000000000000000000000000000000..61d6937f445f77108044e10115db3c43f659ceba --- /dev/null +++ b/src/BrainIAC/MCIclassification/infer_mci.py @@ -0,0 +1,142 @@ +import torch +import pandas as pd +import os +from tqdm import tqdm +from torch.utils.data import DataLoader +from torch.cuda.amp import autocast +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, accuracy_score, precision_score, recall_score, f1_score, roc_auc_score +import numpy as np +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from dataset2 import MedicalImageDatasetBalancedIntensity3D +from model import Backbone, SingleScanModelBP, Classifier +from utils import BaseConfig + + + +def calculate_metrics(pred_probs, pred_labels, true_labels): + """ + classification metrics. + Args: + pred_probs (numpy.ndarray): Predicted probabilities + pred_labels (numpy.ndarray): Predicted labels + true_labels (numpy.ndarray): Ground truth labels + + Returns: + dict: Dictionary containing accuracy, precision, recall, F1, and AUC metrics + """ + accuracy = accuracy_score(true_labels, pred_labels) + precision = precision_score(true_labels, pred_labels) + recall = recall_score(true_labels, pred_labels) + f1 = f1_score(true_labels, pred_labels) + auc = roc_auc_score(true_labels, pred_probs) + + return { + 'accuracy': accuracy, + 'precision': precision, + 'recall': recall, + 'f1': f1, + 'auc': auc + } + + +#============================ +# INFERENCE CLASS +#============================ +class MCIInference(BaseConfig): + """ + Inference class for MCI classification model. + """ + + def __init__(self): + super().__init__() + self.setup_model() + self.setup_data() + + def setup_model(self): + config = self.get_config() + self.backbone = Backbone() + self.classifier = Classifier(d_model=2048, num_classes=1) # Binary classification + self.model = SingleScanModelBP(self.backbone, self.classifier) + + # Load weights + checkpoint = torch.load(config["infer"]["checkpoints"], map_location=self.device, weights_only=False) + self.model.load_state_dict(checkpoint["model_state_dict"], strict=False) + self.model = self.model.to(self.device) + self.model.eval() + print("Model and checkpoint loaded!") + + ## spin up data loaders + def setup_data(self): + config = self.get_config() + self.test_dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=config["data"]["test_csv"], + root_dir=config["data"]["root_dir"] + ) + self.test_loader = DataLoader( + self.test_dataset, + batch_size=1, + shuffle=False, + collate_fn=self.custom_collate, + num_workers=1 + ) + + def infer(self): + """ + Run inference pass + + Returns: + dict: Dictionary with evaluation metrics + """ + results_df = pd.DataFrame(columns=['PredictedProb', 'PredictedLabel', 'TrueLabel']) + all_labels = [] + all_predictions = [] + all_probs = [] + + with torch.no_grad(): + for sample in tqdm(self.test_loader, desc="Inference", unit="batch"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + with autocast(): + outputs = self.model(inputs) + + probs = torch.sigmoid(outputs).cpu().numpy().flatten() + preds = (probs > 0.5).astype(int) + + all_labels.extend(labels.cpu().numpy().flatten()) + all_predictions.extend(preds) + all_probs.extend(probs) + + result = pd.DataFrame({ + 'PredictedProb': probs, + 'PredictedLabel': preds, + 'TrueLabel': labels.cpu().numpy().flatten() + }) + + results_df = pd.concat([results_df, result], ignore_index=True) + + # log metrics + """metrics = calculate_metrics( + np.array(all_probs), + np.array(all_predictions), + np.array(all_labels) + ) + + + print("\nTest Set Metrics:") + print(f"Accuracy: {metrics['accuracy']:.4f}") + print(f"Precision: {metrics['precision']:.4f}") + print(f"Recall: {metrics['recall']:.4f}") + print(f"F1 Score: {metrics['f1']:.4f}") + print(f"AUC: {metrics['auc']:.4f}")""" + + # Save results + print("PredictedLabel", results_df["PredictedLabel"][0]) + results_df.to_csv('./data/output/mci_classification_predictions.csv', index=False) + + return None + +if __name__ == "__main__": + inferencer = MCIInference() + metrics = inferencer.infer() \ No newline at end of file diff --git a/src/BrainIAC/MCIclassification/mci.jpeg b/src/BrainIAC/MCIclassification/mci.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b3571b44293d1142d9a95c9966d15314e0b9b4cd --- /dev/null +++ b/src/BrainIAC/MCIclassification/mci.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2b911a21ab5a985d43fbba6144e7718af86b835833553769ee731c0baf57cd4 +size 24145 diff --git a/src/BrainIAC/MCIclassification/train_mci.py b/src/BrainIAC/MCIclassification/train_mci.py new file mode 100644 index 0000000000000000000000000000000000000000..badde93aa7fb0fe6a1984eab6c13ff01beb764b6 --- /dev/null +++ b/src/BrainIAC/MCIclassification/train_mci.py @@ -0,0 +1,265 @@ +import torch +import torch.nn as nn +import torch.optim as optim +from torch.utils.data import DataLoader +import wandb +from tqdm import tqdm +from torch.optim.lr_scheduler import OneCycleLR +from torch.cuda.amp import GradScaler, autocast +import os +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from dataset2 import MedicalImageDatasetBalancedIntensity3D, TransformationMedicalImageDatasetBalancedIntensity3D +from model import Backbone, SingleScanModel, Classifier +from utils import BaseConfig +import numpy as np +from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score + + +def calculate_metrics(pred_probs, pred_labels, true_labels): + """ + classification metrics. + + Args: + pred_probs (numpy.ndarray): Predicted probabilities + pred_labels (numpy.ndarray): Predicted labels + true_labels (numpy.ndarray): Ground truth labels + + Returns: + dict: Dictionary containing accuracy, precision, recall, F1, and AUC + """ + accuracy = accuracy_score(true_labels, pred_labels) + precision = precision_score(true_labels, pred_labels) + recall = recall_score(true_labels, pred_labels) + f1 = f1_score(true_labels, pred_labels) + auc = roc_auc_score(true_labels, pred_probs) + + return { + 'accuracy': accuracy, + 'precision': precision, + 'recall': recall, + 'f1': f1, + 'auc': auc + } + +#============================ +# TRAINER CLASS +#============================ + +class MCITrainer(BaseConfig): + """ + trainer class for MCI classification + """ + + def __init__(self): + super().__init__() + self.setup_wandb() + self.setup_model() + self.setup_data() + self.setup_training() + + def setup_wandb(self): + config = self.get_config() + wandb.init( + project=config['logger']['project_name'], + name=config['logger']['run_name'], + config=config + ) + + def setup_model(self): + self.backbone = Backbone() + # Change classifier to output 1 value for binary classification + self.classifier = Classifier(d_model=2048, num_classes=1) + self.model = SingleScanModel(self.backbone, self.classifier) + + # Load weights from brainiac + config = self.get_config() + if config["train"]["finetune"] == "yes": + checkpoint = torch.load(config["train"]["weights"], map_location=self.device) + state_dict = checkpoint["state_dict"] + filtered_state_dict = {} + for key, value in state_dict.items(): + new_key = key.replace("module.", "backbone.") if key.startswith("module.") else key + filtered_state_dict[new_key] = value + self.model.backbone.load_state_dict(filtered_state_dict, strict=False) + print("Pretrained weights loaded!") + + if config["train"]["freeze"] == "yes": + for param in self.model.backbone.parameters(): + param.requires_grad = False + print("Backbone weights frozen!") + + self.model = self.model.to(self.device) + + ## spinup dataloaders + def setup_data(self): + config = self.get_config() + self.train_dataset = TransformationMedicalImageDatasetBalancedIntensity3D( + csv_path=config['data']['train_csv'], + root_dir=config["data"]["root_dir"] + ) + self.val_dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=config['data']['val_csv'], + root_dir=config["data"]["root_dir"] + ) + + self.train_loader = DataLoader( + self.train_dataset, + batch_size=config["data"]["batch_size"], + shuffle=True, + collate_fn=self.custom_collate, + num_workers=config["data"]["num_workers"] + ) + self.val_loader = DataLoader( + self.val_dataset, + batch_size=1, + shuffle=False, + collate_fn=self.custom_collate, + num_workers=1 + ) + + def setup_training(self): + """ + training setup + """ + config = self.get_config() + # BCE loss + self.criterion = nn.BCEWithLogitsLoss().to(self.device) + self.optimizer = optim.AdamW( + self.model.parameters(), + lr=config['optim']['lr'], + weight_decay=config["optim"]["weight_decay"] + ) + self.scheduler = OneCycleLR( + self.optimizer, + max_lr=config['optim']['lr'], + epochs=config['optim']['max_epochs'], + steps_per_epoch=len(self.train_loader) + ) + self.scaler = GradScaler() + + ## main training loop + def train(self): + config = self.get_config() + max_epochs = config['optim']['max_epochs'] + best_metrics = { + 'val_loss': float('inf'), + 'accuracy': 0, + 'precision': 0, + 'recall': 0, + 'f1': 0, + 'auc': 0 + } + + for epoch in range(max_epochs): + train_loss = self.train_epoch(epoch, max_epochs) + val_loss, metrics = self.validate_epoch(epoch, max_epochs) + + # Save best model based on validation loss and F1 score + if metrics['auc'] > best_metrics['auc']: + print(f"New best model found!") + print(f"Improved Val Loss from {best_metrics['val_loss']:.4f} to {val_loss:.4f}") + print(f"Improved F1 from {best_metrics['f1']:.4f} to {metrics['f1']:.4f}") + best_metrics.update(metrics) + best_metrics['val_loss'] = val_loss + self.save_checkpoint(epoch, val_loss, metrics) + + wandb.finish() + + ## training pass + def train_epoch(self, epoch, max_epochs): + self.model.train() + train_loss = 0.0 + + for sample in tqdm(self.train_loader, desc=f"Training Epoch {epoch}/{max_epochs-1}"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + self.optimizer.zero_grad(set_to_none=True) + with autocast(): + outputs = self.model(inputs) + loss = self.criterion(outputs, labels.unsqueeze(1)) + + self.scaler.scale(loss).backward() + + self.scaler.unscale_(self.optimizer) + torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0) + + self.scaler.step(self.optimizer) + self.scaler.update() + self.scheduler.step() + + train_loss += loss.item() * inputs.size(0) + + train_loss = train_loss / len(self.train_loader.dataset) + wandb.log({"Train Loss": train_loss}) + return train_loss + + ## validation pass + def validate_epoch(self, epoch, max_epochs): + self.model.eval() + val_loss = 0.0 + all_labels = [] + all_preds = [] + all_probs = [] + + with torch.no_grad(): + for sample in tqdm(self.val_loader, desc=f"Validation Epoch {epoch}/{max_epochs-1}"): + inputs = sample['image'].to(self.device) + labels = sample['label'].float().to(self.device) + + outputs = self.model(inputs) + loss = self.criterion(outputs, labels.unsqueeze(1)) + + # Get probabilities and predictions + probs = torch.sigmoid(outputs).cpu().numpy() + preds = (probs > 0.5).astype(int) + + val_loss += loss.item() * inputs.size(0) + all_labels.extend(labels.cpu().numpy().flatten()) + all_preds.extend(preds.flatten()) + all_probs.extend(probs.flatten()) + + val_loss = val_loss / len(self.val_loader.dataset) + metrics = calculate_metrics( + np.array(all_probs), + np.array(all_preds), + np.array(all_labels) + ) + + wandb.log({ + "Val Loss": val_loss, + "Accuracy": metrics['accuracy'], + "Precision": metrics['precision'], + "Recall": metrics['recall'], + "F1 Score": metrics['f1'], + "AUC": metrics['auc'] + }) + + print(f"Epoch {epoch}/{max_epochs-1}") + print(f"Val Loss: {val_loss:.4f}") + print(f"Accuracy: {metrics['accuracy']:.4f}") + print(f"Precision: {metrics['precision']:.4f}") + print(f"Recall: {metrics['recall']:.4f}") + print(f"F1 Score: {metrics['f1']:.4f}") + print(f"AUC: {metrics['auc']:.4f}") + + return val_loss, metrics + + ## save best model + def save_checkpoint(self, epoch, loss, metrics): + config = self.get_config() + checkpoint = { + 'epoch': epoch, + 'model_state_dict': self.model.state_dict(), + 'metrics': metrics + } + save_path = os.path.join( + config['logger']['save_dir'], + config['logger']['save_name'].format(epoch=epoch, loss=loss, metric=metrics['f1']) + ) + torch.save(checkpoint, save_path) + +if __name__ == "__main__": + trainer = MCITrainer() + trainer.train() \ No newline at end of file diff --git a/src/BrainIAC/__init__.py b/src/BrainIAC/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/__pycache__/dataset2.cpython-39.pyc b/src/BrainIAC/__pycache__/dataset2.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88bad52398389608c9eb171347987b28c0761012 Binary files /dev/null and b/src/BrainIAC/__pycache__/dataset2.cpython-39.pyc differ diff --git a/src/BrainIAC/__pycache__/load_brainiac.cpython-39.pyc b/src/BrainIAC/__pycache__/load_brainiac.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d0ded6c76c011d90bbc9a3011b461a80ccbc06a Binary files /dev/null and b/src/BrainIAC/__pycache__/load_brainiac.cpython-39.pyc differ diff --git a/src/BrainIAC/__pycache__/model.cpython-39.pyc b/src/BrainIAC/__pycache__/model.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5506a2857f1801cee2c2acd4593710eeafec73c8 Binary files /dev/null and b/src/BrainIAC/__pycache__/model.cpython-39.pyc differ diff --git a/src/BrainIAC/__pycache__/utils.cpython-39.pyc b/src/BrainIAC/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad0ab42b63c379ea961954033efe00542364c2a5 Binary files /dev/null and b/src/BrainIAC/__pycache__/utils.cpython-39.pyc differ diff --git a/src/BrainIAC/app.py b/src/BrainIAC/app.py new file mode 100644 index 0000000000000000000000000000000000000000..b2f29d5a4b78eb8198a197c3a781a9fbb0d6cab9 --- /dev/null +++ b/src/BrainIAC/app.py @@ -0,0 +1,728 @@ +import os +import torch +import nibabel as nib +from flask import Flask, request, render_template, redirect, url_for, flash, jsonify +import tempfile +import yaml +import traceback # For detailed error printing +import zipfile +import dicom2nifti +import shutil +import subprocess # To run unzip command +import SimpleITK as sitk +import itk +import numpy as np +from scipy.signal import medfilt +import skimage.filters +import cv2 # For Gaussian Blur +import io # For saving plots to memory +import base64 # For encoding plots +import uuid # For unique IDs + +# Configure Matplotlib for non-GUI backend *before* importing pyplot +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt + +# --- Preprocessing Imports --- +try: + # Adjust import path based on Docker structure + # Assumes HD_BET is now at /app/BrainIAC/HD_BET + from HD_BET.run import run_hd_bet + # Import MONAI saliency visualizer + from monai.visualize.gradient_based import GuidedBackpropSmoothGrad +except ImportError as e: + print(f"Could not import HD_BET or MONAI visualize: {e}. Advanced features might fail.") + run_hd_bet = None + GuidedBackpropSmoothGrad = None + +# Import necessary components from your existing modules +from model import Backbone, SingleScanModel, Classifier +# Removed: from dataset2 import NormalSynchronizedTransform3D +# Import specific MONAI transforms needed +from monai.transforms import Resized, ScaleIntensityd # Removed ToTensord, will handle manually + +app = Flask(__name__) +app.secret_key = 'supersecretkey' # Needed for flashing messages + +# --- Constants for Preprocessing --- +APP_DIR = os.path.dirname(__file__) +TEMPLATE_DIR = os.path.join(APP_DIR, "golden_image", "mni_templates") +PARAMS_RIGID_PATH = os.path.join(APP_DIR, "golden_image", "mni_templates", "Parameters_Rigid.txt") +DEFAULT_TEMPLATE_PATH = os.path.join(TEMPLATE_DIR, "nihpd_asym_13.0-18.5_t1w.nii") # Using adult template as default +HD_BET_CONFIG_PATH = os.path.join(APP_DIR, "HD_BET", "config.py") +HD_BET_MODEL_DIR = os.path.join(APP_DIR, "hdbet_model") # Path to copied models + +# --- Configuration Loading --- +def load_config(): + # Assuming config.yml is in the same directory as app.py + config_path = os.path.join(APP_DIR, 'config.yml') + try: + with open(config_path, 'r') as file: + config = yaml.safe_load(file) + # Add default image_size if not present in config + if 'data' not in config: config['data'] = {} + if 'image_size' not in config['data']: config['data']['image_size'] = [128, 128, 128] + + except FileNotFoundError: + print(f"Error: Configuration file not found at {config_path}") + # Provide default config or handle error appropriately + config = { + 'gpu': {'device': 'cpu'}, + 'infer': {'checkpoints': 'checkpoints/brainage_model_latest.pt'}, + 'data': {'image_size': [128, 128, 128]} # Default image size + } + return config + +config = load_config() +# Ensure image_size is available, e.g., from config or a default +DEFAULT_IMAGE_SIZE = (128, 128, 128) +image_size_cfg = config.get('data', {}).get('image_size', DEFAULT_IMAGE_SIZE) +# Validate image_size format +if not isinstance(image_size_cfg, (list, tuple)) or len(image_size_cfg) != 3: + print(f"Warning: Invalid image_size in config ({image_size_cfg}). Using default {DEFAULT_IMAGE_SIZE}.") + image_size = DEFAULT_IMAGE_SIZE +else: + image_size = tuple(image_size_cfg) # Ensure it's a tuple for transforms + +# --- Model Loading --- +def load_model(device, checkpoint_path): + backbone = Backbone() + classifier = Classifier(d_model=2048) # Make sure d_model matches your trained model + model = SingleScanModel(backbone, classifier) + + try: + # Construct absolute path if checkpoint_path is relative + relative_path = config.get('infer', {}).get('checkpoints', 'checkpoints/brainage_model_latest.pt') + # Use path relative to app.py location + checkpoint_path_abs = os.path.join(APP_DIR, relative_path) + + checkpoint = torch.load(checkpoint_path_abs, map_location=device) + # Adjust key if necessary based on how model was saved + if 'model_state_dict' in checkpoint: + model.load_state_dict(checkpoint['model_state_dict']) + else: + model.load_state_dict(checkpoint) + model.to(device) + model.eval() + print(f"Model loaded successfully from {checkpoint_path_abs} onto {device}.") + return model + except FileNotFoundError: + print(f"Error: Checkpoint file not found at {checkpoint_path_abs}") + return None + except Exception as e: + print(f"Error loading model checkpoint: {e}") + traceback.print_exc() + return None + +device = torch.device(config.get('gpu', {}).get('device', 'cpu')) # Default to CPU +model = load_model(device, config) # Pass full config for path finding + +# --- Preprocessing Functions from preprocess_utils.py --- +def bias_field_correction(img_array): + """Performs N4 bias field correction using SimpleITK.""" + image = sitk.GetImageFromArray(img_array) + # Ensure image is float32 for N4 + if image.GetPixelID() != sitk.sitkFloat32: + image = sitk.Cast(image, sitk.sitkFloat32) + maskImage = sitk.OtsuThreshold(image, 0, 1, 200) + corrector = sitk.N4BiasFieldCorrectionImageFilter() + numberFittingLevels = 4 + # Define iterations per level more robustly + max_iters = [min(50 * (2**i), 200) for i in range(numberFittingLevels)] + corrector.SetMaximumNumberOfIterations(max_iters) + # Set convergence threshold (optional, can speed up) + # corrector.SetConvergenceThreshold(1e-6) + print(" Running N4 Bias Field Correction...") + corrected_image = corrector.Execute(image, maskImage) + print(" N4 Correction finished.") + return sitk.GetArrayFromImage(corrected_image) + +def denoise(volume, kernel_size=3): + """Applies median filter for denoising.""" + print(f" Applying median filter denoising (kernel={kernel_size})...") + return medfilt(volume, kernel_size) + +def rescale_intensity(volume, percentils=[0.5, 99.5], bins_num=256): + """Rescales intensity after removing background via Otsu.""" + print(" Rescaling intensity...") + # Ensure input is float for Otsu and calculations + volume_float = volume.astype(np.float32) + try: + t = skimage.filters.threshold_otsu(volume_float, nbins=256) + print(f" Otsu threshold found: {t}") + volume_masked = np.copy(volume_float) + volume_masked[volume_masked < t] = 0 # Apply mask based on original values + obj_volume = volume_masked[np.where(volume_masked > 0)] + except ValueError: # Handle cases with near-uniform intensity + print(" Otsu failed (likely uniform image), skipping background mask.") + obj_volume = volume_float.flatten() + + if obj_volume.size == 0: + print(" Warning: No foreground voxels found after Otsu. Scaling full volume.") + obj_volume = volume_float.flatten() # Fallback to full volume + min_value = np.min(obj_volume) + max_value = np.max(obj_volume) + else: + min_value = np.percentile(obj_volume, percentils[0]) + max_value = np.percentile(obj_volume, percentils[1]) + + print(f" Intensity range used for scaling: [{min_value:.2f}, {max_value:.2f}]") + # Avoid division by zero if max == min + denominator = max_value - min_value + if denominator < 1e-6: denominator = 1e-6 + + # Create a copy to modify for output + output_volume = np.copy(volume_float) + # Apply scaling only to the object volume identified (or full volume as fallback) + if bins_num == 0: + # Scale to 0-1 (float) + output_volume = (volume_float - min_value) / denominator + output_volume = np.clip(output_volume, 0.0, 1.0) # Clip results to [0, 1] + else: + # Scale and bin + output_volume = np.round((volume_float - min_value) / denominator * (bins_num - 1)) + output_volume = np.clip(output_volume, 0, bins_num - 1) # Ensure within bin range + + # Ensure output is float32 for consistency + return output_volume.astype(np.float32) + +def equalize_hist(volume, bins_num=256): + """Performs histogram equalization on non-zero voxels.""" + print(" Performing histogram equalization...") + # Create a mask of non-zero voxels + mask = volume > 1e-6 # Use a small epsilon for float comparison + obj_volume = volume[mask] + + if obj_volume.size == 0: + print(" Warning: No non-zero voxels found for histogram equalization. Skipping.") + return volume # Return original volume if no foreground + + # Compute histogram and CDF on the non-zero voxels + hist, bins = np.histogram(obj_volume, bins_num, range=(obj_volume.min(), obj_volume.max())) + cdf = hist.cumsum() + + # Normalize CDF + cdf_normalized = (bins_num - 1) * cdf / float(cdf[-1]) + + # Interpolate new values for the object volume + equalized_obj_volume = np.interp(obj_volume, bins[:-1], cdf_normalized) + + # Create a copy of the original volume to put the results back + equalized_volume = np.copy(volume) + equalized_volume[mask] = equalized_obj_volume + + # Ensure output is float32 + return equalized_volume.astype(np.float32) + +def enhance(img_array, run_bias_correction=True, kernel_size=3, percentils=[0.5, 99.5], bins_num=256, run_equalize_hist=True): + """Full enhancement pipeline from preprocess_utils.""" + print("Starting enhancement pipeline...") + volume = img_array.astype(np.float32) # Ensure float input + try: + if run_bias_correction: + volume = bias_field_correction(volume) + volume = denoise(volume, kernel_size) + volume = rescale_intensity(volume, percentils, bins_num) + if run_equalize_hist: + volume = equalize_hist(volume, bins_num) + print("Enhancement pipeline finished.") + return volume + except Exception as e: + print(f"Error during enhancement: {e}") + traceback.print_exc() + raise RuntimeError(f"Failed enhancing image: {e}") # Re-raise to stop processing + +# --- Registration Function (modified enhance call) --- +def register_image(input_nifti_path, output_nifti_path): + """Registers input NIfTI to the default template using Elastix.""" + print(f"Registering {input_nifti_path} to {DEFAULT_TEMPLATE_PATH}") + if not os.path.exists(PARAMS_RIGID_PATH): + raise FileNotFoundError(f"Elastix parameter file not found at {PARAMS_RIGID_PATH}") + if not os.path.exists(DEFAULT_TEMPLATE_PATH): + raise FileNotFoundError(f"Default template file not found at {DEFAULT_TEMPLATE_PATH}") + + fixed_image = itk.imread(DEFAULT_TEMPLATE_PATH, itk.F) + moving_image = itk.imread(input_nifti_path, itk.F) + + parameter_object = itk.ParameterObject.New() + parameter_object.AddParameterFile(PARAMS_RIGID_PATH) + + result_image, _ = itk.elastix_registration_method( + fixed_image, moving_image, + parameter_object=parameter_object, + log_to_console=False # Keep console clean + ) + itk.imwrite(result_image, output_nifti_path) + print(f"Registration output saved to {output_nifti_path}") + +# --- Enhanced Image Function (calls actual enhance) --- +def run_enhance_on_file(input_nifti_path, output_nifti_path): + """Reads NIfTI, runs enhance pipeline, saves NIfTI.""" + print(f"Running full enhancement on {input_nifti_path}") + img_sitk = sitk.ReadImage(input_nifti_path) + img_array = sitk.GetArrayFromImage(img_sitk) + + # Run the actual enhancement pipeline + enhanced_array = enhance(img_array, run_bias_correction=True) # Assuming N4 is desired + + enhanced_img_sitk = sitk.GetImageFromArray(enhanced_array) + enhanced_img_sitk.CopyInformation(img_sitk) # Preserve metadata + sitk.WriteImage(enhanced_img_sitk, output_nifti_path) + print(f"Enhanced image saved to {output_nifti_path}") + +# --- Skull Stripping Function (Set Environment Variable) --- +def run_skull_stripping(input_nifti_path, output_dir): + """Runs HD-BET skull stripping.""" + print(f"Running HD-BET skull stripping on {input_nifti_path}") + if run_hd_bet is None: + raise RuntimeError("HD-BET module could not be imported. Cannot perform skull stripping.") + + # Removed environment variable setting as path is fixed in HD_BET/paths.py + # # Set environment variable *before* calling run_hd_bet + # # Ensure the target directory exists + # if not os.path.isdir(HD_BET_MODEL_DIR): + # raise FileNotFoundError(f"HD-BET model directory not found at specified path: {HD_BET_MODEL_DIR}") + # print(f"Setting HD_BET_MODELS environment variable to: {HD_BET_MODEL_DIR}") + # os.environ['HD_BET_MODELS'] = HD_BET_MODEL_DIR + + # Check config path + if not os.path.exists(HD_BET_CONFIG_PATH): + alt_config_path = os.path.join(APP_DIR, "HD_BET", "HD_BET", "config.py") + if os.path.exists(alt_config_path): + print(f"Warning: Using alternative HD-BET config path: {alt_config_path}") + config_to_use = alt_config_path + else: + raise FileNotFoundError(f"HD-BET config file not found at {HD_BET_CONFIG_PATH} or {alt_config_path}") + else: + config_to_use = HD_BET_CONFIG_PATH + + # Define output paths + base_name = os.path.basename(input_nifti_path).replace(".nii.gz", "").replace(".nii", "") + output_file_path = os.path.join(output_dir, f"{base_name}_bet.nii.gz") + output_mask_path = os.path.join(output_dir, f"{base_name}_bet_mask.nii.gz") + + # Make sure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Run HD-BET + run_hd_bet(input_nifti_path, output_file_path, + mode="fast", + device='cpu', + config_file=config_to_use, + postprocess=False, + do_tta=False, + keep_mask=True, + overwrite=True) + + # Unset environment variable after use (optional, good practice) + # del os.environ['HD_BET_MODELS'] + + if not os.path.exists(output_file_path): + raise RuntimeError(f"HD-BET did not produce the expected output file: {output_file_path}") + + print(f"Skull stripping output saved to {output_file_path}") + return output_file_path, output_mask_path + +# --- Image Preprocessing --- +# Define necessary MONAI transforms directly +# Keys must match the dictionary keys we create later ('image') +resize_transform = Resized(keys=["image"], spatial_size=image_size) +scale_transform = ScaleIntensityd(keys=["image"], minv=0.0, maxv=1.0) + +def preprocess_nifti(nifti_path): + """Loads and preprocesses a NIfTI file, returning a 5D tensor.""" + print(f"Preprocessing NIfTI: {nifti_path}") + scan_data = nib.load(nifti_path).get_fdata() + print(f" Loaded scan data shape: {scan_data.shape}") + scan_tensor = torch.tensor(scan_data, dtype=torch.float32).unsqueeze(0) # Add C dim + print(f" Shape after tensor+channel: {scan_tensor.shape}") + sample = {"image": scan_tensor} + sample_resized = resize_transform(sample) + print(f" Shape after resize: {sample_resized['image'].shape}") + sample_scaled = scale_transform(sample_resized) + print(f" Shape after scaling: {sample_scaled['image'].shape}") + input_tensor = sample_scaled["image"].unsqueeze(0).to(device) # Add B dim + print(f" Final shape for model: {input_tensor.shape}") + if input_tensor.dim() != 5: + raise ValueError(f"Preprocessing resulted in incorrect shape: {input_tensor.shape}. Expected 5D.") + return input_tensor + +# --- Final NIfTI Preprocessing for Model --- +def preprocess_nifti_for_model(nifti_path): + """Loads final NIfTI and prepares 5D tensor for the model.""" + # ... (Same as previous preprocess_nifti function) ... + print(f"Preprocessing NIfTI for model: {nifti_path}") + scan_data = nib.load(nifti_path).get_fdata() + print(f" Loaded scan data shape: {scan_data.shape}") + scan_tensor = torch.tensor(scan_data, dtype=torch.float32).unsqueeze(0) # Add C dim + print(f" Shape after tensor+channel: {scan_tensor.shape}") + sample = {"image": scan_tensor} + sample_resized = resize_transform(sample) + print(f" Shape after resize: {sample_resized['image'].shape}") + sample_scaled = scale_transform(sample_resized) + print(f" Shape after scaling: {sample_scaled['image'].shape}") + input_tensor = sample_scaled["image"].unsqueeze(0).to(device) # Add B dim + print(f" Final shape for model: {input_tensor.shape}") + if input_tensor.dim() != 5: + raise ValueError(f"Preprocessing resulted in incorrect shape: {input_tensor.shape}. Expected 5D.") + return input_tensor + +# --- Saliency Map Generation --- +def generate_saliency(model, input_tensor_5d): + """Generates saliency map using GuidedBackpropSmoothGrad.""" + if GuidedBackpropSmoothGrad is None: + raise ImportError("MONAI visualize components not imported. Cannot generate saliency map.") + if model is None: + raise ValueError("Model not loaded. Cannot generate saliency map.") + + print("Generating saliency map...") + input_tensor_5d.requires_grad_(True) + # Use the backbone for saliency as in the original script + # Ensure model and backbone are on the correct device (CPU in this case) + visualizer = GuidedBackpropSmoothGrad(model=model.backbone.to(device), + stdev_spread=0.15, + n_samples=10, + magnitude=True) + + try: + with torch.enable_grad(): + saliency_map_5d = visualizer(input_tensor_5d.to(device)) + print("Saliency map generated.") + + # Detach, move to CPU, remove Batch and Channel dims for processing/plotting -> (D, H, W) + input_3d = input_tensor_5d.squeeze().cpu().detach().numpy() + saliency_3d = saliency_map_5d.squeeze().cpu().detach().numpy() + + return input_3d, saliency_3d + + except Exception as e: + print(f"Error during saliency map generation: {e}") + traceback.print_exc() + # Return None or empty arrays if generation fails + return None, None + finally: + # Ensure requires_grad is turned off if it was modified + input_tensor_5d.requires_grad_(False) + +# --- Plotting Function for Single Slice --- +def create_plot_images_for_slice(mri_data_3d, saliency_data_3d, slice_index): + """Creates base64 encoded PNGs for a specific axial slice index.""" + print(f" Generating plots for slice index: {slice_index}") + if mri_data_3d is None or saliency_data_3d is None: + print(" Input or Saliency data is None, cannot generate plot.") + return None + if slice_index < 0 or slice_index >= mri_data_3d.shape[2]: + print(f" Error: Slice index {slice_index} out of bounds (0-{mri_data_3d.shape[2]-1}).") + return None + + # Function to save plot to base64 string (copied from previous version) + def save_plot_to_base64(fig): + buf = io.BytesIO() + fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=75) + plt.close(fig) # Close the figure immediately + buf.seek(0) + img_str = base64.b64encode(buf.read()).decode('utf-8') + buf.close() + return img_str + + try: + mri_slice = mri_data_3d[:, :, slice_index] + saliency_slice_orig = saliency_data_3d[:, :, slice_index] + + # --- Normalize MRI Slice (using volume stats if available, otherwise slice stats) --- + # For consistency, ideally pass volume stats, but recalculating per slice is fallback + p1_vol, p99_vol = np.percentile(mri_data_3d, (1, 99)) + mri_norm_denom = p99_vol - p1_vol + if mri_norm_denom < 1e-6: mri_norm_denom = 1e-6 + mri_slice_norm = np.clip(mri_slice, p1_vol, p99_vol) + mri_slice_norm = (mri_slice_norm - p1_vol) / mri_norm_denom + + # --- Process Saliency Slice --- + saliency_slice = np.copy(saliency_slice_orig) + saliency_slice[saliency_slice < 0] = 0 # Ensure non-negative + saliency_slice_blurred = cv2.GaussianBlur(saliency_slice, (15, 15), 0) + # Use volume max for normalization if possible, fallback to slice max + s_max_vol = np.max(saliency_data_3d[saliency_data_3d >= 0]) # Max of non-negative values in volume + if s_max_vol < 1e-6: s_max_vol = 1e-6 + # --- Add logging for the calculated global max --- + print(f" Calculated Global Max Saliency (s_max_vol) for normalization: {s_max_vol:.4f}") + # -------------------------------------------------- + saliency_slice_norm = saliency_slice_blurred / s_max_vol + threshold_value = 0.0 + saliency_slice_thresholded = np.where(saliency_slice_norm > threshold_value, saliency_slice_norm, 0) + + # --- Generate Plots --- + slice_plots = {} + + # Plot 1: Input Slice + fig1, ax1 = plt.subplots(figsize=(3, 3)) + ax1.imshow(mri_slice_norm, cmap='gray', interpolation='none', origin='lower') + ax1.axis('off') + slice_plots['input_slice'] = save_plot_to_base64(fig1) + + # Plot 2: Saliency Heatmap + fig2, ax2 = plt.subplots(figsize=(3, 3)) + ax2.imshow(saliency_slice_thresholded, cmap='magma', interpolation='none', origin='lower') + ax2.axis('off') + slice_plots['heatmap_slice'] = save_plot_to_base64(fig2) + + # Plot 3: Overlay + fig3, ax3 = plt.subplots(figsize=(3, 3)) + ax3.imshow(mri_slice_norm, cmap='gray', interpolation='none', origin='lower') + if np.max(saliency_slice_thresholded) > 0: + # Remove fixed levels to let contour auto-determine levels based on slice data + ax3.contour(saliency_slice_thresholded, cmap='magma', origin='lower', linewidths=1.0) + ax3.axis('off') + slice_plots['overlay_slice'] = save_plot_to_base64(fig3) + + print(f" Generated plots successfully for slice {slice_index}.") + return slice_plots + + except Exception as e: + print(f"Error generating plots for slice {slice_index}: {e}") + traceback.print_exc() + return None + +# --- Flask Routes --- +@app.route('/', methods=['GET']) +def index(): + return render_template('index.html') + +@app.route('/predict', methods=['POST']) +def predict(): + if model is None: + flash('Model not loaded. Cannot perform prediction.', 'error') + return redirect(url_for('index')) + + # Get form data + file_type = request.form.get('file_type') + run_preprocess_flag = request.form.get('preprocess') == 'yes' + generate_saliency_flag = request.form.get('generate_saliency') == 'yes' # Get saliency flag + file = request.files.get('scan_file') + + # --- Basic Input Validation --- + if not file_type: + flash('Please select a file type (NIfTI or DICOM).', 'error') + return redirect(url_for('index')) + if not file or file.filename == '': + flash('No scan file selected', 'error') + return redirect(url_for('index')) + + print(f"Received upload: type='{file_type}', filename='{file.filename}', preprocess={run_preprocess_flag}, saliency={generate_saliency_flag}") + + # --- Setup Temporary Directory --- + # temp_dir_obj = tempfile.TemporaryDirectory() # <--- PROBLEM: Cleans up automatically + # Use mkdtemp to create a persistent temporary directory + # NOTE: Requires a manual cleanup strategy later! + try: + temp_dir = tempfile.mkdtemp() + except Exception as e: + print(f"Error creating temporary directory: {e}") + flash("Server error: Could not create temporary directory.", "error") + return redirect(url_for('index')) + + # Generate a unique ID based on the temp directory name + unique_id = os.path.basename(temp_dir) + print(f"Created persistent temp directory: {temp_dir} (ID: {unique_id})") + nifti_for_preprocessing_path = None # Path to the NIfTI before optional preprocessing + + try: + # --- Handle Upload and DICOM Conversion --- + # --- Handle NIfTI Upload --- + if file_type == 'nifti': + if not file.filename.endswith('.nii.gz'): + flash('Invalid file type for NIfTI selection. Please upload .nii.gz', 'error') + # temp_dir_obj.cleanup() # No object to cleanup, need manual rmtree + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + uploaded_file_path = os.path.join(temp_dir, "uploaded_scan.nii.gz") + file.save(uploaded_file_path) + print(f"Saved uploaded NIfTI file to: {uploaded_file_path}") + nifti_for_preprocessing_path = uploaded_file_path + + # --- Handle DICOM Upload --- + elif file_type == 'dicom': + if not file.filename.endswith('.zip'): + flash('Invalid file type for DICOM selection. Please upload a .zip file.', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + uploaded_zip_path = os.path.join(temp_dir, "dicom_files.zip") + file.save(uploaded_zip_path) + print(f"Saved uploaded DICOM zip to: {uploaded_zip_path}") + dicom_input_dir = os.path.join(temp_dir, "dicom_input") + nifti_output_dir = os.path.join(temp_dir, "nifti_output") + os.makedirs(dicom_input_dir, exist_ok=True) + os.makedirs(nifti_output_dir, exist_ok=True) + try: + # Use shutil.unpack_archive for better cross-platform compatibility potentially + shutil.unpack_archive(uploaded_zip_path, dicom_input_dir) + print(f"Unzip successful.") + except Exception as e: + print(f"Unzip failed: {e}") + flash(f'Error unzipping DICOM file: {e}', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + try: + dicom2nifti.convert_directory(dicom_input_dir, nifti_output_dir, compression=True, reorient=True) + nifti_files = [f for f in os.listdir(nifti_output_dir) if f.endswith('.nii.gz')] + if not nifti_files: + raise RuntimeError("dicom2nifti did not produce a .nii.gz file.") + nifti_for_preprocessing_path = os.path.join(nifti_output_dir, nifti_files[0]) + print(f"DICOM conversion successful. NIfTI file: {nifti_for_preprocessing_path}") + except Exception as e: + print(f"DICOM to NIfTI conversion failed: {e}") + flash(f'Error converting DICOM to NIfTI: {e}', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + else: + flash('Invalid file type selected.', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + + if not nifti_for_preprocessing_path or not os.path.exists(nifti_for_preprocessing_path): + flash('Error: Could not find the NIfTI file for processing.', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + + # --- Optional Preprocessing Steps --- + nifti_to_predict_path = nifti_for_preprocessing_path + if run_preprocess_flag: + print("--- Running Optional Preprocessing Pipeline ---") + try: + registered_path = os.path.join(temp_dir, "registered.nii.gz") + register_image(nifti_for_preprocessing_path, registered_path) + enhanced_path = os.path.join(temp_dir, "enhanced.nii.gz") + run_enhance_on_file(registered_path, enhanced_path) + skullstrip_output_dir = os.path.join(temp_dir, "skullstripped") + skullstripped_path, _ = run_skull_stripping(enhanced_path, skullstrip_output_dir) + nifti_to_predict_path = skullstripped_path + print("--- Optional Preprocessing Pipeline Complete ---") + except Exception as e: + print(f"Error during optional preprocessing pipeline: {e}") + traceback.print_exc() + flash(f'Error during preprocessing: {e}', 'error') + # temp_dir_obj.cleanup() + shutil.rmtree(temp_dir, ignore_errors=True) + return redirect(url_for('index')) + else: + print("--- Skipping Optional Preprocessing Pipeline ---") + + # --- Final Preprocessing for Model & Prediction --- + input_tensor_5d = preprocess_nifti_for_model(nifti_to_predict_path) + print("Performing prediction...") + with torch.no_grad(): + output = model(input_tensor_5d) + predicted_age = output.item() + predicted_age_years = predicted_age / 12 # Adjust if needed + print(f"Prediction successful: {predicted_age_years:.2f} years") + + # --- Saliency Data Handling (Generate, Save, Get Initial Plot) --- + saliency_output_for_template = None # Initialize + if generate_saliency_flag: + print("--- Generating & Saving Saliency Data ---") + try: + input_3d_for_plot, saliency_3d = generate_saliency(model, input_tensor_5d) + + if input_3d_for_plot is not None and saliency_3d is not None: + num_slices = input_3d_for_plot.shape[2] + center_slice_index = num_slices // 2 + + # Save the numpy arrays for the dynamic route + input_array_path = os.path.join(temp_dir, f"{unique_id}_input.npy") + saliency_array_path = os.path.join(temp_dir, f"{unique_id}_saliency.npy") + np.save(input_array_path, input_3d_for_plot) + np.save(saliency_array_path, saliency_3d) + print(f"Saved input array to {input_array_path}") + print(f"Saved saliency array to {saliency_array_path}") + + # Generate ONLY the center slice plots for the initial view + center_slice_plots = create_plot_images_for_slice(input_3d_for_plot, saliency_3d, center_slice_index) + + if center_slice_plots: + # Prepare data structure for the template + saliency_output_for_template = { + 'center_slice_plots': center_slice_plots, + 'num_slices': num_slices, + 'center_slice_index': center_slice_index, + 'unique_id': unique_id, # Pass the ID for filenames + 'temp_dir_path': temp_dir # Pass the full path for lookup + } + print("--- Saliency Data Saved & Initial Plot Generated ---") + else: + print("--- Center Slice Plotting Failed ---") + flash('Failed to generate initial saliency plot.', 'warning') + else: + print("--- Saliency Generation Failed --- ") + flash('Saliency map generation failed.', 'warning') + + except Exception as e: + print(f"Error during saliency processing/saving: {e}") + traceback.print_exc() + flash('Could not generate or save saliency maps due to an error.', 'error') + + # Render result, passing prediction and potentially the NEW saliency structure + return render_template('index.html', + prediction=f"{predicted_age_years:.2f} years", + saliency_info=saliency_output_for_template) # Pass the new dict + + except Exception as e: + flash(f'Error processing file: {e}', 'error') + print(f"Caught Exception during prediction process: {e}") + traceback.print_exc() + # Ensure cleanup happens even if exception occurs mid-process + # temp_dir_obj.cleanup() + if temp_dir and os.path.exists(temp_dir): + shutil.rmtree(temp_dir, ignore_errors=True) # Manual cleanup on general error + return redirect(url_for('index')) + + # NOTE: Temporary directory created with mkdtemp is NOT automatically cleaned. + # Need a separate mechanism (e.g., cron job, background task) to remove old directories + # from the system's temporary location (e.g., /tmp) based on age. + # Leaving the directory here so /get_slice can access the files. + +# --- New Route for Dynamic Slice Loading --- +@app.route('/get_slice//') +def get_slice(unique_id, slice_index): + # Get the actual temporary directory path from query parameter + temp_dir_path = request.args.get('path') + if not temp_dir_path: + print("Error: 'path' query parameter missing in /get_slice request") + return jsonify({"error": "Required path information missing."}), 400 + + # Construct paths using the provided directory path and unique ID + input_array_path = os.path.join(temp_dir_path, f"{unique_id}_input.npy") + saliency_array_path = os.path.join(temp_dir_path, f"{unique_id}_saliency.npy") + print(f"Attempting to load slice {slice_index} for ID {unique_id} from actual path: {temp_dir_path}") + + try: + # Check using the exact paths constructed above + if not os.path.exists(input_array_path) or not os.path.exists(saliency_array_path): + print(f"Error: .npy files not found for ID {unique_id} at {temp_dir_path}") + return jsonify({"error": "Saliency data not found. It might have expired or failed to save."}), 404 + + input_3d = np.load(input_array_path) + saliency_3d = np.load(saliency_array_path) + print(f"Loaded arrays for ID {unique_id}. Input shape: {input_3d.shape}, Saliency shape: {saliency_3d.shape}") + + # Generate plots for the requested slice using the helper function + slice_plots = create_plot_images_for_slice(input_3d, saliency_3d, slice_index) + + if slice_plots: + return jsonify(slice_plots) # Return plot data as JSON + else: + return jsonify({"error": f"Failed to generate plots for slice {slice_index}."}), 500 + + except Exception as e: + print(f"Error in /get_slice for ID {unique_id}, slice {slice_index}: {e}") + traceback.print_exc() + return jsonify({"error": "An internal error occurred while fetching the slice data."}), 500 + +if __name__ == '__main__': + # Use '0.0.0.0' to make it accessible outside the container + app.run(host='0.0.0.0', port=5000, debug=False) # Turn off debug for production/docker \ No newline at end of file diff --git a/src/BrainIAC/app_gradio.py b/src/BrainIAC/app_gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..cce42071d8c901bf1cbbf086b202d8ef8dcdda7a --- /dev/null +++ b/src/BrainIAC/app_gradio.py @@ -0,0 +1,757 @@ +import os +import torch +import nibabel as nib +import gradio as gr +import tempfile +import yaml +import traceback # For detailed error printing +import zipfile +import dicom2nifti +import shutil +import subprocess # To run unzip command +import SimpleITK as sitk +import itk +import numpy as np +from scipy.signal import medfilt +import skimage.filters +import cv2 # For Gaussian Blur +import io # For saving plots to memory +import base64 # For encoding plots +import uuid # For unique IDs +import matplotlib.pyplot as plt +import matplotlib +matplotlib.use('Agg') # Use non-interactive backend + +# --- Custom CSS to hide buttons in logo columns --- +# REVERTING CSS CHANGES AS THEY HID THE LOGOS +# custom_css = """ +# .logo-column button { +# /* display: none !important; */ /* This seemed to hide the whole component */ +# visibility: hidden !important; /* Try making it invisible instead */ +# } +# """ + +# --- Potential Import Issues (Check Paths in Docker/Local) --- +try: + # Assumes HD_BET is now at /app/BrainIAC/HD_BET or adjacent in src + from HD_BET.run import run_hd_bet + from monai.visualize.gradient_based import GuidedBackpropSmoothGrad +except ImportError as e: + print(f"Warning: Could not import HD_BET or MONAI visualize: {e}. Saliency/Preprocessing might fail.") + run_hd_bet = None + GuidedBackpropSmoothGrad = None + +# Import necessary components from your existing modules +from model import Backbone, SingleScanModel, Classifier +from monai.transforms import Resized, ScaleIntensityd + +# --- Constants --- +APP_DIR = os.path.dirname(__file__) +TEMPLATE_DIR = os.path.join(APP_DIR, "golden_image", "mni_templates") +PARAMS_RIGID_PATH = os.path.join(APP_DIR, "golden_image", "mni_templates", "Parameters_Rigid.txt") +DEFAULT_TEMPLATE_PATH = os.path.join(TEMPLATE_DIR, "nihpd_asym_13.0-18.5_t1w.nii") +HD_BET_CONFIG_PATH = os.path.join(APP_DIR, "HD_BET", "config.py") # May need adjustment based on actual HD_BET location +HD_BET_MODEL_DIR = os.path.join(APP_DIR, "hdbet_model") # Path to copied models + +# --- Configuration Loading --- +def load_config(): + config_path = os.path.join(APP_DIR, 'config.yml') + try: + with open(config_path, 'r') as file: + config = yaml.safe_load(file) + if 'data' not in config: config['data'] = {} + if 'image_size' not in config['data']: config['data']['image_size'] = [128, 128, 128] + except FileNotFoundError: + print(f"Warning: Configuration file not found at {config_path}. Using defaults.") + config = { + 'gpu': {'device': 'cpu'}, + 'infer': {'checkpoints': 'checkpoints/mci_model.pt'}, # Updated for new MCI model filename + 'data': {'image_size': [128, 128, 128]} + } + return config + +config = load_config() +DEFAULT_IMAGE_SIZE = (128, 128, 128) +image_size_cfg = config.get('data', {}).get('image_size', DEFAULT_IMAGE_SIZE) +if not isinstance(image_size_cfg, (list, tuple)) or len(image_size_cfg) != 3: + print(f"Warning: Invalid image_size in config ({image_size_cfg}). Using default {DEFAULT_IMAGE_SIZE}.") + image_size = DEFAULT_IMAGE_SIZE +else: + image_size = tuple(image_size_cfg) + +# --- Model Loading --- +def load_model(cfg): + device = torch.device(cfg.get('gpu', {}).get('device', 'cpu')) + backbone = Backbone() + classifier = Classifier(d_model=2048, num_classes=1) # Binary classification for MCI + model = SingleScanModel(backbone, classifier) # Using BP model for MCI classification + relative_path = cfg.get('infer', {}).get('checkpoints', 'checkpoints/mci_model.pt') + checkpoint_path_abs = os.path.join(APP_DIR, relative_path) + try: + print(f"Loading MCI classification model from: {checkpoint_path_abs}") + checkpoint = torch.load(checkpoint_path_abs, map_location=device, weights_only=False) + state_dict = checkpoint.get('model_state_dict', checkpoint) + model.load_state_dict(state_dict, strict=False) + model.to(device) + model.eval() + print(f"MCI classification model loaded successfully onto {device}") + return model, device + except FileNotFoundError: + print(f"Error: Checkpoint file not found at {checkpoint_path_abs}") + return None, device + except Exception as e: + print(f"Error loading model checkpoint: {e}") + traceback.print_exc() + return None, device + +model, device = load_model(config) + +# --- Preprocessing Functions (Copied/Adapted from app.py) --- +def bias_field_correction(img_array): + print(" Running N4 Bias Field Correction...") + image = sitk.GetImageFromArray(img_array) + if image.GetPixelID() != sitk.sitkFloat32: + image = sitk.Cast(image, sitk.sitkFloat32) + maskImage = sitk.OtsuThreshold(image, 0, 1, 200) + corrector = sitk.N4BiasFieldCorrectionImageFilter() + numberFittingLevels = 4 + max_iters = [min(50 * (2**i), 200) for i in range(numberFittingLevels)] + corrector.SetMaximumNumberOfIterations(max_iters) + corrected_image = corrector.Execute(image, maskImage) + print(" N4 Correction finished.") + return sitk.GetArrayFromImage(corrected_image) + +def denoise(volume, kernel_size=3): + print(f" Applying median filter denoising (kernel={kernel_size})...") + return medfilt(volume, kernel_size) + +def rescale_intensity(volume, percentils=[0.5, 99.5], bins_num=256): + print(" Rescaling intensity...") + volume_float = volume.astype(np.float32) + try: + t = skimage.filters.threshold_otsu(volume_float, nbins=256) + volume_masked = np.copy(volume_float) + volume_masked[volume_masked < t] = 0 + obj_volume = volume_masked[np.where(volume_masked > 0)] + except ValueError: + print(" Otsu failed, skipping background mask.") + obj_volume = volume_float.flatten() + if obj_volume.size == 0: + print(" Warning: No foreground voxels found. Scaling full volume.") + obj_volume = volume_float.flatten() + min_value = np.min(obj_volume) + max_value = np.max(obj_volume) + else: + min_value = np.percentile(obj_volume, percentils[0]) + max_value = np.percentile(obj_volume, percentils[1]) + denominator = max_value - min_value + if denominator < 1e-6: denominator = 1e-6 + output_volume = np.copy(volume_float) + if bins_num == 0: + output_volume = (volume_float - min_value) / denominator + output_volume = np.clip(output_volume, 0.0, 1.0) + else: + output_volume = np.round((volume_float - min_value) / denominator * (bins_num - 1)) + output_volume = np.clip(output_volume, 0, bins_num - 1) + return output_volume.astype(np.float32) + +def equalize_hist(volume, bins_num=256): + print(" Performing histogram equalization...") + mask = volume > 1e-6 + obj_volume = volume[mask] + if obj_volume.size == 0: + print(" Warning: No non-zero voxels. Skipping equalization.") + return volume + hist, bins = np.histogram(obj_volume, bins_num, range=(obj_volume.min(), obj_volume.max())) + cdf = hist.cumsum() + cdf_normalized = (bins_num - 1) * cdf / float(cdf[-1]) + equalized_obj_volume = np.interp(obj_volume, bins[:-1], cdf_normalized) + equalized_volume = np.copy(volume) + equalized_volume[mask] = equalized_obj_volume + return equalized_volume.astype(np.float32) + +def enhance(img_array, run_bias_correction=True, kernel_size=3, percentils=[0.5, 99.5], bins_num=256, run_equalize_hist=True): + print("Starting enhancement pipeline...") + volume = img_array.astype(np.float32) + try: + if run_bias_correction: volume = bias_field_correction(volume) + volume = denoise(volume, kernel_size) + volume = rescale_intensity(volume, percentils, bins_num) + if run_equalize_hist: volume = equalize_hist(volume, bins_num) + print("Enhancement pipeline finished.") + return volume + except Exception as e: + print(f"Error during enhancement: {e}") + traceback.print_exc() + raise RuntimeError(f"Failed enhancing image: {e}") + +def register_image(input_nifti_path, output_nifti_path): + print(f"Registering {input_nifti_path} to {DEFAULT_TEMPLATE_PATH}") + if not all(os.path.exists(p) for p in [PARAMS_RIGID_PATH, DEFAULT_TEMPLATE_PATH]): + raise FileNotFoundError("Elastix parameter or template file not found.") + fixed_image = itk.imread(DEFAULT_TEMPLATE_PATH, itk.F) + moving_image = itk.imread(input_nifti_path, itk.F) + parameter_object = itk.ParameterObject.New() + parameter_object.AddParameterFile(PARAMS_RIGID_PATH) + result_image, _ = itk.elastix_registration_method(fixed_image, moving_image, parameter_object=parameter_object, log_to_console=False) + itk.imwrite(result_image, output_nifti_path) + print(f"Registration output saved to {output_nifti_path}") + +def run_enhance_on_file(input_nifti_path, output_nifti_path): + print(f"Running full enhancement on {input_nifti_path}") + img_sitk = sitk.ReadImage(input_nifti_path) + img_array = sitk.GetArrayFromImage(img_sitk) + enhanced_array = enhance(img_array, run_bias_correction=True) + enhanced_img_sitk = sitk.GetImageFromArray(enhanced_array) + enhanced_img_sitk.CopyInformation(img_sitk) + sitk.WriteImage(enhanced_img_sitk, output_nifti_path) + print(f"Enhanced image saved to {output_nifti_path}") + +def run_skull_stripping(input_nifti_path, output_dir): + print(f"Running HD-BET skull stripping on {input_nifti_path}") + if run_hd_bet is None: raise RuntimeError("HD-BET module not imported.") + if not os.path.exists(HD_BET_CONFIG_PATH): raise FileNotFoundError(f"HD-BET config not found at {HD_BET_CONFIG_PATH}") + if not os.path.isdir(HD_BET_MODEL_DIR): raise FileNotFoundError(f"HD-BET models not found at {HD_BET_MODEL_DIR}") + + base_name = os.path.basename(input_nifti_path).replace(".nii.gz", "").replace(".nii", "") + output_file_path = os.path.join(output_dir, f"{base_name}_bet.nii.gz") + output_mask_path = os.path.join(output_dir, f"{base_name}_bet_mask.nii.gz") + os.makedirs(output_dir, exist_ok=True) + + try: + run_hd_bet(input_nifti_path, output_file_path, mode="fast", device='cpu', config_file=HD_BET_CONFIG_PATH, postprocess=False, do_tta=False, keep_mask=True, overwrite=True) + finally: + pass + + if not os.path.exists(output_file_path): raise RuntimeError("HD-BET did not produce output file.") + print(f"Skull stripping output saved to {output_file_path}") + return output_file_path, output_mask_path + +# --- MONAI Transforms --- +resize_transform = Resized(keys=["image"], spatial_size=image_size) +scale_transform = ScaleIntensityd(keys=["image"], minv=0.0, maxv=1.0) + +def preprocess_nifti_for_model(nifti_path): + print(f"Preprocessing NIfTI for model: {nifti_path}") + scan_data = nib.load(nifti_path).get_fdata() + scan_tensor = torch.tensor(scan_data, dtype=torch.float32).unsqueeze(0) # Add C dim + sample = {"image": scan_tensor} + sample_resized = resize_transform(sample) + sample_scaled = scale_transform(sample_resized) + input_tensor = sample_scaled["image"].unsqueeze(0).to(device) # Add B dim + if input_tensor.dim() != 5: raise ValueError(f"Preprocessing resulted in incorrect shape: {input_tensor.shape}") + print(f" Final shape for model: {input_tensor.shape}") + return input_tensor + +# --- Saliency Generation --- +def generate_saliency(model_to_use, input_tensor_5d): + if GuidedBackpropSmoothGrad is None: raise ImportError("MONAI visualize components not imported.") + if model_to_use is None: raise ValueError("Model not loaded.") + print("Generating saliency map...") + input_tensor_5d.requires_grad_(True) + visualizer = GuidedBackpropSmoothGrad(model=model_to_use.backbone.to(device), stdev_spread=0.15, n_samples=10, magnitude=True) + try: + with torch.enable_grad(): + saliency_map_5d = visualizer(input_tensor_5d.to(device)) + input_3d = input_tensor_5d.squeeze().cpu().detach().numpy() + saliency_3d = saliency_map_5d.squeeze().cpu().detach().numpy() + print("Saliency map generated.") + return input_3d, saliency_3d + except Exception as e: + print(f"Error during saliency map generation: {e}") + traceback.print_exc() + return None, None + finally: + input_tensor_5d.requires_grad_(False) + +# --- Plotting Function (Returns NumPy arrays for Gradio) --- +def create_slice_plots(mri_data_3d, saliency_data_3d, slice_index): + print(f" Generating plots for slice index: {slice_index}") + if mri_data_3d is None or saliency_data_3d is None: return None, None, None + + # Change from the third dimension (axis 2, sagittal) to the first dimension (axis 0, axial) + if not (0 <= slice_index < mri_data_3d.shape[0]): + print(f" Error: Slice index {slice_index} out of bounds (0-{mri_data_3d.shape[0]-1}).") + return None, None, None + + # Function to save plot to NumPy array + def save_plot_to_numpy(fig): + with io.BytesIO() as buf: + fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=75) # Adjust DPI as needed + plt.close(fig) + buf.seek(0) + img_arr = plt.imread(buf, format='png') + # Return RGBA array, can be simplified if only grayscale needed for input + return (img_arr * 255).astype(np.uint8) + + try: + # Extract axial slice instead of sagittal + mri_slice = mri_data_3d[slice_index, :, :] + saliency_slice_orig = saliency_data_3d[slice_index, :, :] + + # Normalize MRI Slice (using volume stats) + p1_vol, p99_vol = np.percentile(mri_data_3d, (1, 99)) + mri_norm_denom = max(p99_vol - p1_vol, 1e-6) + mri_slice_norm = np.clip((mri_slice - p1_vol) / mri_norm_denom, 0, 1) + + # Process Saliency Slice + saliency_slice = np.copy(saliency_slice_orig) + saliency_slice[saliency_slice < 0] = 0 + saliency_slice_blurred = cv2.GaussianBlur(saliency_slice, (15, 15), 0) + s_max_vol = max(np.max(saliency_data_3d[saliency_data_3d >= 0]), 1e-6) + saliency_slice_norm = saliency_slice_blurred / s_max_vol + saliency_slice_thresholded = np.where(saliency_slice_norm > 0.0, saliency_slice_norm, 0) # Threshold slightly > 0 + + # Plot 1: Input Slice + fig1, ax1 = plt.subplots(figsize=(6, 6)) + ax1.imshow(mri_slice_norm, cmap='gray', interpolation='none', origin='lower') + ax1.axis('off') + input_plot_np = save_plot_to_numpy(fig1) + + # Plot 2: Saliency Heatmap + fig2, ax2 = plt.subplots(figsize=(6, 6)) + ax2.imshow(saliency_slice_thresholded, cmap='magma', interpolation='none', origin='lower', vmin=0) # Set vmin + ax2.axis('off') + heatmap_plot_np = save_plot_to_numpy(fig2) + + # Plot 3: Overlay + fig3, ax3 = plt.subplots(figsize=(6, 6)) + ax3.imshow(mri_slice_norm, cmap='gray', interpolation='none', origin='lower') + if np.max(saliency_slice_thresholded) > 0: + ax3.contour(saliency_slice_thresholded, cmap='magma', origin='lower', linewidths=1.0, levels=np.linspace(saliency_slice_thresholded.min(), saliency_slice_thresholded.max(), 5)) # Adjust levels + ax3.axis('off') + overlay_plot_np = save_plot_to_numpy(fig3) + + print(f" Generated numpy plots successfully for slice {slice_index}.") + return input_plot_np, heatmap_plot_np, overlay_plot_np + + except Exception as e: + print(f"Error generating numpy plots for slice {slice_index}: {e}") + traceback.print_exc() + return None, None, None + +# Add this function after the create_slice_plots function +def create_probability_gauge(probability): + """ + Creates a gauge visualization for the MCI probability. + + Args: + probability (float): A value between 0 and 1 representing the MCI probability. + + Returns: + numpy.ndarray: A numpy array containing the gauge visualization image. + """ + # Create a figure with a polar projection + fig = plt.figure(figsize=(8, 4)) + ax = fig.add_subplot(111, polar=True) + + # Set the min and max angles for the gauge (in radians) + # -pi/2 to pi/2 creates a half-circle (180 degrees) + theta_min = -np.pi/2 + theta_max = np.pi/2 + + # Calculate the angle for the needle based on probability (0 to 1) + needle_angle = theta_min + probability * (theta_max - theta_min) + + # Create a color gradient for the gauge background + cmap = plt.cm.RdYlGn_r # Red-Yellow-Green colormap (reversed) + + # Draw the gauge background + theta = np.linspace(theta_min, theta_max, 100) + radii = np.ones_like(theta) + + # Create color array for the gauge segments + norm = plt.Normalize(0, 1) + colors = cmap(np.linspace(0, 1, len(theta))) + + # Draw colored bars for the gauge + width = (theta_max - theta_min) / len(theta) + bars = ax.bar(theta, radii, width=width, bottom=0.0, alpha=0.8, linewidth=0) + + # Set the color for each bar + for bar, color in zip(bars, colors): + bar.set_facecolor(color) + + # Add the needle + needle_length = 0.9 + ax.annotate('', xy=(needle_angle, needle_length), xytext=(needle_angle, 0), + arrowprops=dict(arrowstyle='wedge', color='black', lw=2)) + + # Add boundary markers and labels - move them lower by adjusting y position (1.1 -> 1.3) + ax.text(theta_min, 2.2, 'Healthy Control', ha='left', va='center', fontsize=12) + ax.text(theta_max, 1.3, 'MCI', ha='right', va='center', fontsize=12) + + # Add the probability text below the gauge + prob_text = f"Probability: {probability:.2f}" + fig.text(0.5, 0.15, prob_text, ha='center', va='center', fontsize=14, fontweight='bold') + + # Set the limits and remove unnecessary elements + ax.set_ylim(0, 1.4) # Increased the upper limit to accommodate the lower labels + ax.set_theta_zero_location('N') # 0 at the top + ax.set_theta_direction(-1) # clockwise + ax.set_thetagrids([]) # Remove angle labels + ax.grid(False) # Remove grid + ax.set_rgrids([]) # Remove radial labels + ax.spines['polar'].set_visible(False) # Remove the outer circle + + # Convert figure to numpy array + with io.BytesIO() as buf: + fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, dpi=100) + plt.close(fig) + buf.seek(0) + img_arr = plt.imread(buf, format='png') + + return (img_arr * 255).astype(np.uint8) + +# --- Gradio Processing Function --- +def process_scan(file_type, uploaded_file, run_preprocess, generate_saliency_flag): + if model is None: + raise gr.Error("Model is not loaded. Cannot perform prediction.") + if uploaded_file is None: + raise gr.Error("No file uploaded.") + + temp_dir = tempfile.mkdtemp() + print(f"Created temp directory: {temp_dir}") + nifti_for_preprocessing_path = None + error_message = None + prediction_text = "Processing..." + # Initialize outputs to None or placeholder images/values + input_slice_img, heatmap_slice_img, overlay_slice_img = None, None, None + probability_gauge = None + saliency_state = {"input_path": None, "saliency_path": None, "num_slices": 0} + slider_update = gr.Slider(value=0, minimum=0, maximum=1, visible=False) # Initially hidden, use max=1 to avoid log(0) error + + try: + # --- Handle Upload and DICOM Conversion --- + file_path = uploaded_file.name # Get path from Gradio file object + filename = os.path.basename(file_path) + print(f"Processing '{filename}' (type: {file_type})") + + if file_type == 'NIfTI': + # Check if the filename ends with either .nii or .nii.gz + if not (filename.lower().endswith('.nii.gz') or filename.lower().endswith('.nii')): + raise gr.Error("Invalid NIfTI file. Please upload .nii or .nii.gz") + + # Define the destination path (always .nii.gz for consistency) + dest_path = os.path.join(temp_dir, "uploaded_scan.nii.gz") + nifti_for_preprocessing_path = dest_path + + # Check if the uploaded file is uncompressed .nii + if filename.lower().endswith('.nii') and not filename.lower().endswith('.nii.gz'): + print(f"Detected uncompressed .nii file: {filename}. Compressing to {dest_path}") + try: + # Load the uncompressed .nii file + img = nib.load(file_path) + # Save it as a compressed .nii.gz file + nib.save(img, dest_path) + print(f"Successfully compressed and saved to: {dest_path}") + except Exception as e: + raise gr.Error(f"Failed to load or compress .nii file: {e}") + else: + # If it's already .nii.gz, just copy it + print(f"Copying compressed NIfTI {filename} to: {dest_path}") + shutil.copy(file_path, dest_path) + + # nifti_for_preprocessing_path is already set to dest_path + # print(f"NIfTI path for preprocessing: {nifti_for_preprocessing_path}") # Redundant logging + + elif file_type == 'DICOM (zip)': + if not filename.endswith('.zip'): + raise gr.Error("Invalid DICOM file. Please upload a .zip archive.") + uploaded_zip_path = os.path.join(temp_dir, "dicom_files.zip") + shutil.copy(file_path, uploaded_zip_path) + print(f"Copied DICOM zip to: {uploaded_zip_path}") + dicom_input_dir = os.path.join(temp_dir, "dicom_input") + nifti_output_dir = os.path.join(temp_dir, "nifti_output") + os.makedirs(dicom_input_dir, exist_ok=True) + os.makedirs(nifti_output_dir, exist_ok=True) + try: + shutil.unpack_archive(uploaded_zip_path, dicom_input_dir) + print("Unzip successful.") + except Exception as e: + raise gr.Error(f"Error unzipping DICOM file: {e}") + try: + dicom2nifti.convert_directory(dicom_input_dir, nifti_output_dir, compression=True, reorient=True) + nifti_files = [f for f in os.listdir(nifti_output_dir) if f.endswith('.nii.gz')] + if not nifti_files: raise RuntimeError("dicom2nifti did not produce a .nii.gz file.") + nifti_for_preprocessing_path = os.path.join(nifti_output_dir, nifti_files[0]) + print(f"DICOM conversion successful. NIfTI: {nifti_for_preprocessing_path}") + except Exception as e: + raise gr.Error(f"Error converting DICOM to NIfTI: {e}") + else: + raise gr.Error("Invalid file type selected.") + + if not nifti_for_preprocessing_path or not os.path.exists(nifti_for_preprocessing_path): + raise gr.Error("Could not find the NIfTI file after initial processing.") + + # --- Optional Preprocessing --- + nifti_to_predict_path = nifti_for_preprocessing_path + if run_preprocess: + print("--- Running Optional Preprocessing Pipeline ---") + try: + registered_path = os.path.join(temp_dir, "registered.nii.gz") + register_image(nifti_for_preprocessing_path, registered_path) + enhanced_path = os.path.join(temp_dir, "enhanced.nii.gz") + run_enhance_on_file(registered_path, enhanced_path) + skullstrip_output_dir = os.path.join(temp_dir, "skullstripped") + skullstripped_path, _ = run_skull_stripping(enhanced_path, skullstrip_output_dir) + nifti_to_predict_path = skullstripped_path + print("--- Optional Preprocessing Pipeline Complete ---") + except Exception as e: + raise gr.Error(f"Error during preprocessing: {e}") + else: + print("--- Skipping Optional Preprocessing Pipeline ---") + + # --- Prediction (Changed for MCI Classification) --- + input_tensor_5d = preprocess_nifti_for_model(nifti_to_predict_path) + print("Performing MCI classification prediction...") + with torch.no_grad(): + try: + output = model(input_tensor_5d) + + # Convert output to probability + if isinstance(output, torch.Tensor): + logit = output.item() + else: + logit = output + + # Apply sigmoid to get probability + probability = torch.sigmoid(torch.tensor(logit)).item() + predicted_class = 1 if probability > 0.5 else 0 + class_label = "MCI" if predicted_class == 1 else "Healthy Control" + + # Create the probability gauge visualization + probability_gauge = create_probability_gauge(probability) + + # Format prediction text for classification + prediction_text = f"Prediction: {class_label} " + except Exception as pred_error: + print(f"Error during prediction: {pred_error}") + traceback.print_exc() + raise gr.Error(f"Failed to make prediction: {pred_error}") + + print(prediction_text) + + # --- Saliency Map Generation --- + if generate_saliency_flag: + print("--- Generating Saliency Data ---") + try: + input_3d, saliency_3d = generate_saliency(model, input_tensor_5d) + if input_3d is not None and saliency_3d is not None: + num_slices = input_3d.shape[0] # Using axial slices now (first dimension) + center_slice_index = num_slices // 2 + + # Save numpy arrays to the temp dir for the slider callback + unique_id = str(uuid.uuid4()) + input_array_path = os.path.join(temp_dir, f"{unique_id}_input.npy") + saliency_array_path = os.path.join(temp_dir, f"{unique_id}_saliency.npy") + np.save(input_array_path, input_3d) + np.save(saliency_array_path, saliency_3d) + print(f"Saved input array: {input_array_path}") + print(f"Saved saliency array: {saliency_array_path}") + + # Generate initial plots for the center slice + input_slice_img, heatmap_slice_img, overlay_slice_img = create_slice_plots(input_3d, saliency_3d, center_slice_index) + + # Update state for the slider callback + saliency_state = { + "input_path": input_array_path, + "saliency_path": saliency_array_path, + "num_slices": num_slices + } + # Update and show the slider + slider_update = gr.Slider(value=center_slice_index, minimum=0, maximum=num_slices - 1, step=1, label="Select Slice", visible=True) + print("--- Saliency Generated and Initial Plot Created ---") + else: + error_message = "Saliency map generation failed." + print(f"Warning: {error_message}") + + except ImportError as e: + error_message = f"Cannot generate saliency: {e}" + print(f"Warning: {error_message}") + except Exception as e: + error_message = f"Error during saliency processing: {e}" + traceback.print_exc() + print(f"Warning: {error_message}") + + except Exception as e: + print(f"Error in process_scan: {e}") + traceback.print_exc() + # Use gr.Warning for non-fatal errors shown to user + if error_message: # Prepend specific error if available + gr.Warning(f"{error_message}. General error: {e}") + else: + gr.Warning(f"An error occurred: {e}") + # Return default/error states for outputs + return "Error during processing", None, None, None, None, gr.Slider(visible=False), {"input_path": None, "saliency_path": None, "num_slices": 0} + + finally: + # Optional: Schedule cleanup of the temp_dir if files aren't needed long-term + # Be cautious if files ARE needed by slider state. Gradio might handle this? + # shutil.rmtree(temp_dir, ignore_errors=True) + # print(f"Cleaned up temp directory: {temp_dir}") # <--- Defer cleanup + pass + + + # Return results including the probability gauge + return prediction_text, input_slice_img, heatmap_slice_img, overlay_slice_img, probability_gauge, slider_update, saliency_state + +# --- Gradio Slider Update Function --- +def update_slice_viewer(slice_index, current_state): + input_path = current_state.get("input_path") + saliency_path = current_state.get("saliency_path") + + if not input_path or not saliency_path or not os.path.exists(input_path) or not os.path.exists(saliency_path): + print(f"Warning: Cannot update slice viewer. Missing or invalid numpy array paths in state: {current_state}") + # Return None or placeholder images to indicate error + return None, None, None + + try: + input_3d = np.load(input_path) + saliency_3d = np.load(saliency_path) + num_slices = input_3d.shape[0] # Using axial slices (first dimension) + + # Ensure slice_index is valid (Gradio slider should handle bounds, but double-check) + slice_index = int(slice_index) + if not (0 <= slice_index < num_slices): + print(f"Warning: Invalid slice index {slice_index} received by update function.") + return None, None, None # Or return previous plots? + + # Generate new plots for the selected slice + input_slice_img, heatmap_slice_img, overlay_slice_img = create_slice_plots(input_3d, saliency_3d, slice_index) + + return input_slice_img, heatmap_slice_img, overlay_slice_img + + except Exception as e: + print(f"Error updating slice viewer for index {slice_index}: {e}") + traceback.print_exc() + # Return None or indicate error + return None, None, None + + +# --- Build Gradio Interface --- +with gr.Blocks(css=""" +#header-row { + min-height: 150px; + align-items: center; +} +.logo-img img { + height: 150px; + object-fit: contain; +} +.probability-gauge { + display: flex; + justify-content: center; + margin-top: 1rem; +} +""") as demo: + # Header Row with Logos and Title + with gr.Row(elem_id="header-row"): + with gr.Column(scale=1): + gr.Image(os.path.join(APP_DIR, "static/images/kannlab.png"), + show_label=False, interactive=False, + show_download_button=False, + container=False, + elem_classes=["logo-img"]) + with gr.Column(scale=3): + gr.Markdown( + "

" + "BrainIAC: MCI Classification" + "

" + ) + with gr.Column(scale=1): + gr.Image(os.path.join(APP_DIR, "static/images/brainiac.jpeg"), + show_label=False, interactive=False, + show_download_button=False, + container=False, + elem_classes=["logo-img"]) + + # --- Add model description section --- + with gr.Accordion("ℹ️ Model Details and Usage Guide", open=False): + gr.Markdown(""" +### 🧠 BrainIAC: MCI Classification + +**Model Description** +A 3D ResNet50 model trained to predict Mild Cognitive Impairment (MCI) from T1-weighted MRI scans. + +**Training Dataset** +- **Subjects**: Trained on T1-weighted MRI scans from subjects with MCI and healthy controls +- **Imaging Modality**: T1-weighted MRI +- **Preprocessing**: Registration to MNI, N4 bias correction, histogram equalization, skull stripping + +**Input** +- Format: NIfTI or zipped DICOM +- Required sequence: T1w (3D) + +**Output** +- Binary classification: MCI or Healthy Control +- Probability score for MCI + +**Intended Use** +- Research use only! + +**NOTE** +- Not validated on T2, FLAIR, DWI or other sequences +- Not validated on pathological cases beyond MCI +- Upload PHI data at own risk! +- The model is hosted on a cloud-based CPU instance. +- The data is not stored, shared or collected for any purpose! + +""") + + # Use gr.State to store paths to numpy arrays for the slider callback + saliency_state = gr.State({"input_path": None, "saliency_path": None, "num_slices": 0}) + + # Main Content Row (Controls Left, Output Right) + with gr.Row(): + with gr.Column(scale=1): + with gr.Group(): + gr.Markdown("### Controls") + file_type = gr.Radio(["NIfTI", "DICOM (zip)"], label="Select Input File Type", value="NIfTI") + scan_file = gr.File(label="Upload Scan File") + run_preprocess = gr.Checkbox(label="Run Preprocessing Pipeline ", value=False) + generate_saliency_checkbox = gr.Checkbox(label="Generate Saliency Maps ", value=True) + submit_btn = gr.Button("Classify MCI", variant="primary") + + with gr.Column(scale=3): + with gr.Group(): + gr.Markdown("### Classification Result") + prediction_output = gr.Label(label="Classification Result") + # Add the probability gauge visualization + gr.Markdown("

MCI Probability

") + probability_gauge = gr.Image(label="Probability Gauge", type="numpy", show_label=False, elem_classes=["probability-gauge"]) + + with gr.Group(): + gr.Markdown("### Saliency Map Viewer (Axial Slice)") + slice_slider = gr.Slider(label="Select Slice", minimum=0, maximum=0, step=1, value=0, visible=False) + with gr.Row(): + with gr.Column(): + gr.Markdown("

Input Slice

") + input_slice_img = gr.Image(label="Input Slice", type="numpy", show_label=False) + with gr.Column(): + gr.Markdown("

Saliency Heatmap

") + heatmap_slice_img = gr.Image(label="Saliency Heatmap", type="numpy", show_label=False) + with gr.Column(): + gr.Markdown("

Overlay

") + overlay_slice_img = gr.Image(label="Overlay", type="numpy", show_label=False) + + # --- Wire Components --- + submit_btn.click( + fn=process_scan, + inputs=[file_type, scan_file, run_preprocess, generate_saliency_checkbox], + outputs=[prediction_output, input_slice_img, heatmap_slice_img, overlay_slice_img, probability_gauge, slice_slider, saliency_state] + ) + + slice_slider.change( + fn=update_slice_viewer, + inputs=[slice_slider, saliency_state], + outputs=[input_slice_img, heatmap_slice_img, overlay_slice_img] + ) + +# --- Launch the App --- +if __name__ == "__main__": + if model is None: + print("ERROR: Model failed to load. Gradio app cannot start.") + else: + print("Launching Gradio Interface...") + demo.launch(server_name="0.0.0.0", server_port=7860, debug=False, share=False) diff --git a/src/BrainIAC/checkpoints/__init__.py b/src/BrainIAC/checkpoints/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/checkpoints/mci_model.pt b/src/BrainIAC/checkpoints/mci_model.pt new file mode 100644 index 0000000000000000000000000000000000000000..6df5c762fa2e0621f22c7587bfb0855e23259dbb --- /dev/null +++ b/src/BrainIAC/checkpoints/mci_model.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:152490617007f608a96896e014e3321d22f50426273d928f07ce3fceabf4794d +size 184972165 diff --git a/src/BrainIAC/config.yml b/src/BrainIAC/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..5b0ee43626ea9cdcf96516319bc37639f220736d --- /dev/null +++ b/src/BrainIAC/config.yml @@ -0,0 +1,28 @@ +data: + batch_size: 16 + collate: 1 + num_workers: 4 + root_dir: ./data/sample/processed + test_csv: ./data/csvs/input_scans.csv + train_csv: ./data/csvs/train_set_100.csv + val_csv: ./data/csvs/val_set_100.csv +gpu: + device: cpu + visible_device: '' +infer: + checkpoints: ./checkpoints/mci_model.pt +logger: + project_name: brainage + run_name: ExperimentName_trainconfigs + save_dir: ./Checkpoints + save_name: ExperimentName_trainconfigs_checkpoint-{epoch:02d}-{loss:.2f}-{metric:.2f} +optim: + clr: 'no' + lr: 0.0001 + max_epochs: 200 + momentum: 0.9 + weight_decay: 1.0e-05 +train: + finetune: 'yes' + freeze: 'no' + weights: path/to/brainiac/weights diff --git a/src/BrainIAC/data/csvs/brainage.csv b/src/BrainIAC/data/csvs/brainage.csv new file mode 100644 index 0000000000000000000000000000000000000000..0142508025e12c48db6f1915debcf2e4f77158c8 --- /dev/null +++ b/src/BrainIAC/data/csvs/brainage.csv @@ -0,0 +1,2 @@ +pat_id,scandate,label +subpixar009,T1w,42.0 diff --git a/src/BrainIAC/data/csvs/input_scans.csv b/src/BrainIAC/data/csvs/input_scans.csv new file mode 100644 index 0000000000000000000000000000000000000000..5e22b0fb5b082233f8cfe5f4c449f4b288c15807 --- /dev/null +++ b/src/BrainIAC/data/csvs/input_scans.csv @@ -0,0 +1,2 @@ +pat_id,scandate,label +00001,t2f,1 \ No newline at end of file diff --git a/src/BrainIAC/data/csvs/mci.csv b/src/BrainIAC/data/csvs/mci.csv new file mode 100644 index 0000000000000000000000000000000000000000..8e8534800c70904f9fabb53319553eb318b6b43d --- /dev/null +++ b/src/BrainIAC/data/csvs/mci.csv @@ -0,0 +1,2 @@ +pat_id,scandate,label +subpixar009,T1w,0 \ No newline at end of file diff --git a/src/BrainIAC/data/csvs/sequenceclass.csv b/src/BrainIAC/data/csvs/sequenceclass.csv new file mode 100644 index 0000000000000000000000000000000000000000..5e22b0fb5b082233f8cfe5f4c449f4b288c15807 --- /dev/null +++ b/src/BrainIAC/data/csvs/sequenceclass.csv @@ -0,0 +1,2 @@ +pat_id,scandate,label +00001,t2f,1 \ No newline at end of file diff --git a/src/BrainIAC/data/csvs/stroke.csv b/src/BrainIAC/data/csvs/stroke.csv new file mode 100644 index 0000000000000000000000000000000000000000..562dec7da4571578721ffbd8585b8ee3e30f739b --- /dev/null +++ b/src/BrainIAC/data/csvs/stroke.csv @@ -0,0 +1,2 @@ +pat_id,scandate,label +subpixar009,T1w,0.0 diff --git a/src/BrainIAC/data/output/__init__.py b/src/BrainIAC/data/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/data/sample/processed/00001_t1c.nii.gz b/src/BrainIAC/data/sample/processed/00001_t1c.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..ab00e12326e51bd3a33fd2480edd410c47b9989c --- /dev/null +++ b/src/BrainIAC/data/sample/processed/00001_t1c.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4399faadcc45c8a4541313cdf88aced7d835ed59ac3078d950e0eac293d603f5 +size 2901768 diff --git a/src/BrainIAC/data/sample/processed/00001_t1n.nii.gz b/src/BrainIAC/data/sample/processed/00001_t1n.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..cdefdc498322d0e737c8427445f7ea14fa043886 --- /dev/null +++ b/src/BrainIAC/data/sample/processed/00001_t1n.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e860924b936e301ddeba20409fbb59dde322475cb49328f1b46a9235c792e73e +size 2614637 diff --git a/src/BrainIAC/data/sample/processed/00001_t2f.nii.gz b/src/BrainIAC/data/sample/processed/00001_t2f.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..fcc44846b1045df8dfb8127531d54bee07d2df30 --- /dev/null +++ b/src/BrainIAC/data/sample/processed/00001_t2f.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82aed8546af5e6d8d94fd91c56227abdcf6120130390d1556c4342a208980604 +size 2802807 diff --git a/src/BrainIAC/data/sample/processed/00001_t2w.nii.gz b/src/BrainIAC/data/sample/processed/00001_t2w.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..e15aaa1564ff91ecc8497b08a5243489abb71380 --- /dev/null +++ b/src/BrainIAC/data/sample/processed/00001_t2w.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cd389cc57d12134a30a898c66532228126b9a7d0600ee578e82a32144528b51 +size 2714287 diff --git a/src/BrainIAC/data/sample/processed/subpixar009_T1w.nii.gz b/src/BrainIAC/data/sample/processed/subpixar009_T1w.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..8f1b9054c5f5f99941fcc12094cf23e1bc966ccf --- /dev/null +++ b/src/BrainIAC/data/sample/processed/subpixar009_T1w.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e46cc233952a663f6e5f89e07e5f5f436744d190772ee2a5ca1c639fff1f15a +size 1465387 diff --git a/src/BrainIAC/dataset2.py b/src/BrainIAC/dataset2.py new file mode 100644 index 0000000000000000000000000000000000000000..701f9afddfaaffda74283693d81fe1c7a99d48d9 --- /dev/null +++ b/src/BrainIAC/dataset2.py @@ -0,0 +1,168 @@ +import os +import torch +import pandas as pd +from torch.utils.data import Dataset +import nibabel as nib +from monai.transforms import Affined, RandGaussianNoised, Rand3DElasticd, AdjustContrastd, ScaleIntensityd, ToTensord, Resized, RandRotate90d, Resize, RandGaussianSmoothd, GaussianSmoothd, Rotate90d, StdShiftIntensityd, RandAdjustContrastd, Flipd +import random +import numpy as np + + +####################################### +## 3D SYNC TRANSFORM +####################################### + +class NormalSynchronizedTransform3D: + """ Vanilla Validation Transforms""" + + def __init__(self, image_size=(128,128,128), max_rotation=40, translate_range=0.2, scale_range=(0.9, 1.3), apply_prob=0.5): + self.image_size = image_size + self.max_rotation = max_rotation + self.translate_range = translate_range + self.scale_range = scale_range + self.apply_prob = apply_prob + + def __call__(self, scan_list): + transformed_scans = [] + resize_transform = Resized(spatial_size=(128,128,128), keys=["image"]) + scale_transform = ScaleIntensityd(keys=["image"], minv=0.0, maxv=1.0) # Intensity scaling + tensor_transform = ToTensord(keys=["image"]) # Convert to tensor + + for scan in scan_list: + sample = {"image": scan} + sample = resize_transform(sample) + sample = scale_transform(sample) + sample = tensor_transform(sample) + transformed_scans.append(sample["image"].squeeze()) + + return torch.stack(transformed_scans) + +class MedicalImageDatasetBalancedIntensity3D(Dataset): + """ Validation Dataset class """ + + def __init__(self, csv_path, root_dir, transform=None): + self.dataframe = pd.read_csv(csv_path, dtype={"pat_id":str, "scandate":str}) + self.root_dir = root_dir + self.transform = NormalSynchronizedTransform3D() + + def __len__(self): + return len(self.dataframe) + + def __getitem__(self, idx): + if torch.is_tensor(idx): + idx = idx.tolist() + + ## load the niftis from csv + pat_id = str(self.dataframe.loc[idx, 'pat_id']) + scan_dates = str(self.dataframe.loc[idx, 'scandate']) + label = self.dataframe.loc[idx, 'label'] + scandates = scan_dates.split('-') + scan_list = [] + + + for scandate in scandates: + img_name = os.path.join(self.root_dir , f"{pat_id}_{scandate}.nii.gz") + scan = nib.load(img_name).get_fdata() + scan_list.append(torch.tensor(scan, dtype=torch.float32).unsqueeze(0)) + + ## package into a dictionary for val loader + transformed_scans = self.transform(scan_list) + sample = {"image": transformed_scans, "label": torch.tensor(label, dtype=torch.float32), "pat_id": pat_id} + return sample + + +class SynchronizedTransform3D: + """ Trainign Augmentation method """ + + def __init__(self, image_size=(128,128,128), max_rotation=0.34, translate_range=15, scale_range=(0.9, 1.3), apply_prob=0.5, gaussian_sigma_range=(0.25, 1.5), gaussian_noise_std_range=(0.05, 0.09)): + self.image_size = image_size + self.max_rotation = max_rotation + self.translate_range = translate_range + self.scale_range = scale_range + self.apply_prob = apply_prob + self.gaussian_sigma_range = gaussian_sigma_range + self.gaussian_noise_std_range = gaussian_noise_std_range + + def __call__(self, scan_list): + transformed_scans = [] + rotate_params = (random.uniform(-self.max_rotation, self.max_rotation),) * 3 if random.random() < self.apply_prob else (0, 0, 0) + translate_params = tuple([random.uniform(-self.translate_range, self.translate_range) for _ in range(3)]) if random.random() < self.apply_prob else (0, 0, 0) + scale_params = tuple([random.uniform(self.scale_range[0], self.scale_range[1]) for _ in range(3)]) if random.random() < self.apply_prob else (1, 1, 1) + gaussian_sigma = tuple([random.uniform(self.gaussian_sigma_range[0], self.gaussian_sigma_range[1]) for _ in range(3)]) if random.random() < self.apply_prob else None + gaussian_noise_std = random.uniform(self.gaussian_noise_std_range[0], self.gaussian_noise_std_range[1]) if random.random() < self.apply_prob else None + flip_axes = (0,1) if random.random() < self.apply_prob else None # Determine if and along which axes to flip + flip_x = 0 if random.random() < self.apply_prob else None + flip_y = 1 if random.random() < self.apply_prob else None + flip_z = 2 if random.random() < self.apply_prob else None + offset = random.randint(50,100) if random.random() < self.apply_prob else None + gammafactor = random.uniform(0.5,2.0) if random.random() < self.apply_prob else 1 + + affine_transform = Affined(keys=["image"], rotate_params=rotate_params, translate_params=translate_params, scale_params=scale_params, padding_mode='zeros') + gaussian_blur_transform = GaussianSmoothd(keys=["image"], sigma=gaussian_sigma) if gaussian_sigma else None + gaussian_noise_transform = RandGaussianNoised(keys=["image"], std=gaussian_noise_std, prob=1.0, mean=0.0, sample_std=False) if gaussian_noise_std else None + #flip_transform = Rotate90d(keys=["image"], k=1, spatial_axes=flip_axes) if flip_axes else None + flip_x_transform = Flipd(keys=["image"], spatial_axis=flip_x) if flip_x else None + flip_y_transform = Flipd(keys=["image"], spatial_axis=flip_y) if flip_y else None + flip_z_transform = Flipd(keys=["image"], spatial_axis=flip_z) if flip_z else None + resize_transform = Resized(spatial_size=(128,128,128), keys=["image"]) + scale_transform = ScaleIntensityd(keys=["image"], minv=0.0, maxv=1.0) # Intensity scaling + tensor_transform = ToTensord(keys=["image"]) # Convert to tensor + shift_intensity = StdShiftIntensityd(keys = ["image"], factor = offset, nonzero=True) + adjust_contrast = AdjustContrastd(keys = ["image"], gamma = gammafactor) + + for scan in scan_list: + sample = {"image": scan} + sample = resize_transform(sample) + sample = affine_transform(sample) + if flip_x_transform: + sample = flip_x_transform(sample) + if flip_y_transform: + sample = flip_y_transform(sample) + if flip_z_transform: + sample = flip_z_transform(sample) + if gaussian_blur_transform: + sample = gaussian_blur_transform(sample) + if offset: + sample = shift_intensity(sample) + sample = scale_transform(sample) + sample = adjust_contrast(sample) + if gaussian_noise_transform: + sample = gaussian_noise_transform(sample) + sample = tensor_transform(sample) + transformed_scans.append(sample["image"].squeeze()) + + return torch.stack(transformed_scans) + + +class TransformationMedicalImageDatasetBalancedIntensity3D(Dataset): + """ Training Dataset class """ + + def __init__(self, csv_path, root_dir, transform=None): + self.dataframe = pd.read_csv(csv_path, dtype={"pat_id":str, "scandate":str}) + self.root_dir = root_dir + self.transform = SynchronizedTransform3D() # calls training augmentations + + def __len__(self): + return len(self.dataframe) + + def __getitem__(self, idx): + if torch.is_tensor(idx): + idx = idx.tolist() + + ## load the niftis from csv + pat_id = str(self.dataframe.loc[idx, 'pat_id']) + scan_dates = str(self.dataframe.loc[idx, 'scandate']) + label = self.dataframe.loc[idx, 'label'] + scandates = scan_dates.split('-') + scan_list = [] + + + for scandate in scandates: + img_name = os.path.join(self.root_dir , f"{pat_id}_{scandate}.nii.gz") #f"{pat_id}_{scandate}.nii.gz") + scan = nib.load(img_name).get_fdata() + scan_list.append(torch.tensor(scan, dtype=torch.float32).unsqueeze(0)) + + # package into a monai type dictionary + transformed_scans = self.transform(scan_list) + sample = {"image": transformed_scans, "label": torch.tensor(label, dtype=torch.float32), "pat_id": pat_id} + return sample diff --git a/src/BrainIAC/get_brainiac_features.py b/src/BrainIAC/get_brainiac_features.py new file mode 100644 index 0000000000000000000000000000000000000000..dda6e2f39d2974bb6478309551216addd00440ce --- /dev/null +++ b/src/BrainIAC/get_brainiac_features.py @@ -0,0 +1,119 @@ +import torch +import numpy as np +import pandas as pd +import random +import yaml +import os +import argparse +from tqdm import tqdm +from torch.utils.data import DataLoader +from dataset2 import MedicalImageDatasetBalancedIntensity3D +from load_brainiac import load_brainiac + +# fix random seed +seed = 42 +random.seed(seed) +np.random.seed(seed) +torch.manual_seed(seed) + + +# Set GPU +os.environ['CUDA_VISIBLE_DEVICES'] = "0" +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") +if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + +# Define custom collate function for data loading +def custom_collate(batch): + images = [item['image'] for item in batch] + labels = [item['label'] for item in batch] + + max_len = 1 + padded_images = [] + + for img in images: + pad_size = max_len - img.shape[0] + if pad_size > 0: + padding = torch.zeros((pad_size,) + img.shape[1:]) + img_padded = torch.cat([img, padding], dim=0) + padded_images.append(img_padded) + else: + padded_images.append(img) + + return {"image": torch.stack(padded_images, dim=0), "label": torch.stack(labels)} + +#========================= +# Inference function +#========================= + +def infer(model, test_loader): + features_df = None # Placeholder for feature DataFrame + model.eval() + + with torch.no_grad(): + for sample in tqdm(test_loader, desc="Inference", unit="batch"): + inputs = sample['image'].to(device) + class_labels = sample['label'].float().to(device) + + # Get features from the model + features = model(inputs) + features_numpy = features.cpu().numpy() + + # Expand features into separate columns + feature_columns = [f'Feature_{i}' for i in range(features_numpy.shape[1])] + batch_features = pd.DataFrame( + features_numpy, + columns=feature_columns + ) + batch_features['GroundTruthClassLabel'] = class_labels.cpu().numpy().flatten() + + # Append batch features to features_df + if features_df is None: + features_df = batch_features + else: + features_df = pd.concat([features_df, batch_features], ignore_index=True) + + return features_df + +#========================= +# Main inference pipeline +#========================= + +def main(): + # argparse + parser = argparse.ArgumentParser(description='Extract BrainIAC features from images') + parser.add_argument('--checkpoint', type=str, required=True, + help='Path to the BrainIAC model checkpoint') + parser.add_argument('--input_csv', type=str, required=True, + help='Path to the input CSV file containing image paths') + parser.add_argument('--output_csv', type=str, required=True, + help='Path to save the output features CSV') + parser.add_argument('--root_dir', type=str, required=True, + help='Root directory containing the image data') + args = parser.parse_args() + + # spinup the dataloader + test_dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=args.input_csv, + root_dir=args.root_dir + ) + test_loader = DataLoader( + test_dataset, + batch_size=1, + shuffle=False, + collate_fn=custom_collate, + num_workers=1 + ) + + # Load brainiac + model = load_brainiac(args.checkpoint, device) + model = model.to(device) + # infer + features_df = infer(model, test_loader) + + # Save features + features_df.to_csv(args.output_csv, index=False) + print(f"Features saved to {args.output_csv}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/BrainIAC/get_brainiac_saliencymap.py b/src/BrainIAC/get_brainiac_saliencymap.py new file mode 100644 index 0000000000000000000000000000000000000000..5c3ce921c2802afe552642f2862ce2af9b0a3ac4 --- /dev/null +++ b/src/BrainIAC/get_brainiac_saliencymap.py @@ -0,0 +1,111 @@ +import torch +import numpy as np +import random +import yaml +import os +import argparse +from tqdm import tqdm +from torch.utils.data import DataLoader +import nibabel as nib +from monai.visualize.gradient_based import SmoothGrad, GuidedBackpropSmoothGrad +from dataset2 import MedicalImageDatasetBalancedIntensity3D +from load_brainiac import load_brainiac + +# Fix random seed +seed = 42 +random.seed(seed) +np.random.seed(seed) +torch.manual_seed(seed) + +# collate funcntion (unneccerary for single timpoint input) +def custom_collate(batch): + """Handles variable size of the scans and pads the sequence dimension.""" + images = [item['image'] for item in batch] + labels = [item['label'] for item in batch] + patids = [item['pat_id'] for item in batch] + + max_len = 1 # singlescan input + padded_images = [] + + for img in images: + pad_size = max_len - img.shape[0] + if pad_size > 0: + padding = torch.zeros((pad_size,) + img.shape[1:]) + img_padded = torch.cat([img, padding], dim=0) + padded_images.append(img_padded) + else: + padded_images.append(img) + + return {"image": torch.stack(padded_images, dim=0), "label": labels, "pat_id": patids} + + +def generate_saliency_maps(model, data_loader, output_dir, device): + """Generate saliency maps using guided backprop method""" + model.eval() + visualizer = GuidedBackpropSmoothGrad(model=model.backbone, stdev_spread=0.15, n_samples=10, magnitude=True) + + for sample in tqdm(data_loader, desc="Generating saliency maps"): + inputs = sample['image'].requires_grad_(True) + patids = sample["pat_id"] + imagename = patids[0] + + input_tensor = inputs.to(device) + + with torch.enable_grad(): + saliency_map = visualizer(input_tensor) + + # Save input image and saliency map + inputs_np = input_tensor.squeeze().cpu().detach().numpy() + saliency_np = saliency_map.squeeze().cpu().detach().numpy() + + input_nifti = nib.Nifti1Image(inputs_np, np.eye(4)) + saliency_nifti = nib.Nifti1Image(saliency_np, np.eye(4)) + + # Save files + nib.save(input_nifti, os.path.join(output_dir, f"{imagename}_image.nii.gz")) + nib.save(saliency_nifti, os.path.join(output_dir, f"{imagename}_saliencymap.nii.gz")) + +def main(): + parser = argparse.ArgumentParser(description='Generate saliency maps for medical images') + parser.add_argument('--checkpoint', type=str, required=True, + help='Path to the model checkpoint') + parser.add_argument('--input_csv', type=str, required=True, + help='Path to the input CSV file containing image paths') + parser.add_argument('--output_dir', type=str, required=True, + help='Directory to save saliency maps') + parser.add_argument('--root_dir', type=str, required=True, + help='Root directory containing the image data') + + args = parser.parse_args() + device = torch.device("cpu") + + # Create output directory if it doesn't exist + os.makedirs(args.output_dir, exist_ok=True) + + # Initialize dataset and dataloader + dataset = MedicalImageDatasetBalancedIntensity3D( + csv_path=args.input_csv, + root_dir=args.root_dir + ) + dataloader = DataLoader( + dataset, + batch_size=1, + shuffle=False, + collate_fn=custom_collate, + num_workers=1 + ) + + # Load brainiac and ensure it's on CPU + model = load_brainiac(args.checkpoint, device) + model = model.to(device) + + # Make sure model weights are on CPU + model.backbone = model.backbone.to(device) + + # Generate saliency maps + generate_saliency_maps(model, dataloader, args.output_dir, device) + + print(f"Saliency maps generated and saved to {args.output_dir}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/BrainIAC/golden_image/.DS_Store b/src/BrainIAC/golden_image/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6cf36feab32f697bfed66e5ec110a0e87f914476 Binary files /dev/null and b/src/BrainIAC/golden_image/.DS_Store differ diff --git a/src/BrainIAC/golden_image/mni_templates/.DS_Store b/src/BrainIAC/golden_image/mni_templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/src/BrainIAC/golden_image/mni_templates/.DS_Store differ diff --git a/src/BrainIAC/golden_image/mni_templates/Parameters_Rigid.txt b/src/BrainIAC/golden_image/mni_templates/Parameters_Rigid.txt new file mode 100644 index 0000000000000000000000000000000000000000..19d729f7e970a3683fe06b2b59a24f27916a516a --- /dev/null +++ b/src/BrainIAC/golden_image/mni_templates/Parameters_Rigid.txt @@ -0,0 +1,141 @@ +// Example parameter file for rotation registration +// C-style comments: // + +// The internal pixel type, used for internal computations +// Leave to float in general. +// NB: this is not the type of the input images! The pixel +// type of the input images is automatically read from the +// images themselves. +// This setting can be changed to "short" to save some memory +// in case of very large 3D images. +(FixedInternalImagePixelType "float") +(MovingInternalImagePixelType "float") + +// **************** Main Components ************************** + +// The following components should usually be left as they are: +(Registration "MultiResolutionRegistration") +(Interpolator "BSplineInterpolator") +(ResampleInterpolator "FinalBSplineInterpolator") +(Resampler "DefaultResampler") + +// These may be changed to Fixed/MovingSmoothingImagePyramid. +// See the manual. +(FixedImagePyramid "FixedRecursiveImagePyramid") +(MovingImagePyramid "MovingRecursiveImagePyramid") + +// The following components are most important: +// The optimizer AdaptiveStochasticGradientDescent (ASGD) works +// quite ok in general. The Transform and Metric are important +// and need to be chosen careful for each application. See manual. +(Optimizer "AdaptiveStochasticGradientDescent") +(Transform "EulerTransform") +(Metric "AdvancedMattesMutualInformation") + +// ***************** Transformation ************************** + +// Scales the rotations compared to the translations, to make +// sure they are in the same range. In general, it's best to +// use automatic scales estimation: +(AutomaticScalesEstimation "true") + +// Automatically guess an initial translation by aligning the +// geometric centers of the fixed and moving. +(AutomaticTransformInitialization "true") + +// Whether transforms are combined by composition or by addition. +// In generally, Compose is the best option in most cases. +// It does not influence the results very much. +(HowToCombineTransforms "Compose") + +// ******************* Similarity measure ********************* + +// Number of grey level bins in each resolution level, +// for the mutual information. 16 or 32 usually works fine. +// You could also employ a hierarchical strategy: +//(NumberOfHistogramBins 16 32 64) +(NumberOfHistogramBins 32) + +// If you use a mask, this option is important. +// If the mask serves as region of interest, set it to false. +// If the mask indicates which pixels are valid, then set it to true. +// If you do not use a mask, the option doesn't matter. +(ErodeMask "false") + +// ******************** Multiresolution ********************** + +// The number of resolutions. 1 Is only enough if the expected +// deformations are small. 3 or 4 mostly works fine. For large +// images and large deformations, 5 or 6 may even be useful. +(NumberOfResolutions 4) + +// The downsampling/blurring factors for the image pyramids. +// By default, the images are downsampled by a factor of 2 +// compared to the next resolution. +// So, in 2D, with 4 resolutions, the following schedule is used: +//(ImagePyramidSchedule 8 8 4 4 2 2 1 1 ) +// And in 3D: +//(ImagePyramidSchedule 8 8 8 4 4 4 2 2 2 1 1 1 ) +// You can specify any schedule, for example: +//(ImagePyramidSchedule 4 4 4 3 2 1 1 1 ) +// Make sure that the number of elements equals the number +// of resolutions times the image dimension. + +// ******************* Optimizer **************************** + +// Maximum number of iterations in each resolution level: +// 200-500 works usually fine for rigid registration. +// For more robustness, you may increase this to 1000-2000. +(MaximumNumberOfIterations 250) + +// The step size of the optimizer, in mm. By default the voxel size is used. +// which usually works well. In case of unusual high-resolution images +// (eg histology) it is necessary to increase this value a bit, to the size +// of the "smallest visible structure" in the image: +//(MaximumStepLength 1.0) + +// **************** Image sampling ********************** + +// Number of spatial samples used to compute the mutual +// information (and its derivative) in each iteration. +// With an AdaptiveStochasticGradientDescent optimizer, +// in combination with the two options below, around 2000 +// samples may already suffice. +(NumberOfSpatialSamples 2048) + +// Refresh these spatial samples in every iteration, and select +// them randomly. See the manual for information on other sampling +// strategies. +(NewSamplesEveryIteration "true") +(ImageSampler "Random") + +// ************* Interpolation and Resampling **************** + +// Order of B-Spline interpolation used during registration/optimisation. +// It may improve accuracy if you set this to 3. Never use 0. +// An order of 1 gives linear interpolation. This is in most +// applications a good choice. +(BSplineInterpolationOrder 1) + +// Order of B-Spline interpolation used for applying the final +// deformation. +// 3 gives good accuracy; recommended in most cases. +// 1 gives worse accuracy (linear interpolation) +// 0 gives worst accuracy, but is appropriate for binary images +// (masks, segmentations); equivalent to nearest neighbor interpolation. +(FinalBSplineInterpolationOrder 3) + +//Default pixel value for pixels that come from outside the picture: +(DefaultPixelValue 0) + +// Choose whether to generate the deformed moving image. +// You can save some time by setting this to false, if you are +// only interested in the final (nonrigidly) deformed moving image +// for example. +(WriteResultImage "true") + +// The pixel type and format of the resulting deformed moving image +(ResultImagePixelType "short") +(ResultImageFormat "mhd") + + diff --git a/src/BrainIAC/golden_image/mni_templates/nihpd_asym_04.5-08.5_t1w.nii b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_04.5-08.5_t1w.nii new file mode 100644 index 0000000000000000000000000000000000000000..9a9d07d6300675c02166186d9a980a7f8d86cf57 --- /dev/null +++ b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_04.5-08.5_t1w.nii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66273629365bd2cde6b2e2e7a74c0de52b703d1f92c12d2e4746aa9b47684e27 +size 17350930 diff --git a/src/BrainIAC/golden_image/mni_templates/nihpd_asym_07.5-13.5_t1w.nii b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_07.5-13.5_t1w.nii new file mode 100644 index 0000000000000000000000000000000000000000..bfb6577cdd506e176d9e8d8522e67d88ac0ee797 --- /dev/null +++ b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_07.5-13.5_t1w.nii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07d05690e2d2635b28673cd4667617c97ae14f648a8923ba7de2fb6578ae61ad +size 17350930 diff --git a/src/BrainIAC/golden_image/mni_templates/nihpd_asym_13.0-18.5_t1w.nii b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_13.0-18.5_t1w.nii new file mode 100644 index 0000000000000000000000000000000000000000..1c3f42145f63f2ef3a7b26e9ac44958491fc8aab --- /dev/null +++ b/src/BrainIAC/golden_image/mni_templates/nihpd_asym_13.0-18.5_t1w.nii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f10804664000688f0ddc124b39ce3ae27f2c339a40583bd6ff916727e97b77d0 +size 17350930 diff --git a/src/BrainIAC/hdbet_model/0.model b/src/BrainIAC/hdbet_model/0.model new file mode 100644 index 0000000000000000000000000000000000000000..23d2336bed49651cb402e47ec75d81588aa5ce8d --- /dev/null +++ b/src/BrainIAC/hdbet_model/0.model @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f75233753c4750672815e2b7a86db754995ae44b8f1cd77bccfc37becd2d83c +size 65443735 diff --git a/src/BrainIAC/healthy_brain_preprocess.py b/src/BrainIAC/healthy_brain_preprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..041e42375b21e94ae51716df77f9c4d31cc85d25 --- /dev/null +++ b/src/BrainIAC/healthy_brain_preprocess.py @@ -0,0 +1,149 @@ +from __future__ import generators + +import logging +import glob, os, functools +import sys +sys.path.append('../') + + +import SimpleITK as sitk +import numpy as np +import scipy +import nibabel as nib +import skimage +import matplotlib.pyplot as plt +import scipy.misc +from scipy import ndimage +from skimage.transform import resize,rescale +import cv2 +import itk +import subprocess +from tqdm import tqdm +import pandas as pd +import warnings +import statistics +import torch +import csv +import os +import yaml + +from HD_BET.run import run_hd_bet # git clone HDBET repo +from dataset.preprocess_utils import enhance, enhance_noN4 +from dataset.preprocess_datasets_T1_to_2d import create_quantile_from_brain + +warnings.filterwarnings('ignore') +cuda_device = '1' +os.environ['CUDA_VISIBLE_DEVICES'] = cuda_device +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") +#torch.cuda.set_device(1) # Set CUDA device to 0 (first GPU) +#net = net.cuda() + +def select_template_based_on_age(age): + for golden_file_path, age_values in age_ranges.items(): + if age_values['min_age'] <= int(age) and int(age) <= age_values['max_age']: + print(golden_file_path) + return golden_file_path + +def register_to_template(input_image_path, output_path, fixed_image_path,rename_id,create_subfolder=True): + fixed_image = itk.imread(fixed_image_path, itk.F) + + # Import Parameter Map + parameter_object = itk.ParameterObject.New() + parameter_object.AddParameterFile('/media/sdb/divyanshu/divyanshu/aidan_segmentation/pediatric-brain-age-main/dataset/golden_image/mni_templates/Parameters_Rigid.txt') + + if "nii" in input_image_path and "._" not in input_image_path: + print(input_image_path) + + # Call registration function + try: + moving_image = itk.imread(input_image_path, itk.F) + result_image, result_transform_parameters = itk.elastix_registration_method( + fixed_image, moving_image, + parameter_object=parameter_object, + log_to_console=False) + image_id = input_image_path.split("/")[-1] + + itk.imwrite(result_image, output_path+"/"+rename_id+".nii.gz") + + print("Registered ", rename_id) + except: + print("Cannot transform", rename_id) + +def outlier_voting(numbers): + mean = statistics.mean(numbers) + stdev = statistics.stdev(numbers) + + threshold = stdev # *2 #*3 + + good_nums_avg =[] + for n in numbers: + if n > mean + threshold or n < mean - threshold: + continue + else: + good_nums_avg.append(n) + + #if len(good_nums_avg)<=3: + # print(len(good_nums_avg)) + return np.average(good_nums_avg) + + +#Data https://openneuro.org/datasets/ds000228/versions/1.1.0 +img_path = '/media/sdb/divyanshu/divyanshu/aidan_segmentation/dummy_t1_preprocess/OAS2_0001_MR1.nii.gz' +data_path = "/media/sdb/divyanshu/divyanshu/longitudinal_fm/datasets/abide/data" +gt_age = 86 # age of subject +gender = "M" # gender +path_to = "/media/sdb/divyanshu/divyanshu/longitudinal_fm/datasets/abide/preprocessed_data" # save to + +# MNI templates http://nist.mni.mcgill.ca/pediatric-atlases-4-5-18-5y/ +age_ranges = {"/media/data/BrainIAC/src/BrainIAC/golden_image/mni_templates/nihpd_asym_04.5-08.5_t1w.nii" : {"min_age":3, "max_age":7}, + "/media/data/BrainIAC/src/BrainIAC/golden_image/mni_templates/nihpd_asym_07.5-13.5_t1w.nii": {"min_age":8, "max_age":13}, + "/media/data/BrainIAC/src/BrainIAC/golden_image/mni_templates/nihpd_asym_13.0-18.5_t1w.nii": {"min_age":14, "max_age":100}} + + +for eachimage in tqdm(os.listdir(data_path), desc="Processing images", unit="image"): + if 1:#"sub" in eachimage: + ## load image + img_path = os.path.join(data_path, eachimage) + nii= nib.load(img_path) + image, affine = nii.get_fdata(), nii.affine + #plt.imshow(image[:,:,100]) + #print(nib.aff2axcodes(affine)) + + # path to store registered image in + new_path_to = path_to#path_to+"/"+img_path.split("/")[-1].split(".")[0] + if eachimage in os.listdir(new_path_to): + print("yay") + else: + if not os.path.exists(path_to): + os.mkdir(path_to) + if not os.path.exists(new_path_to): + os.mkdir(new_path_to) + + # register image to MNI template + golden_file_path = select_template_based_on_age(gt_age) + print("Registering to template:", golden_file_path) + #fun fact: the registering to the template pipeline is not deterministic + register_to_template(img_path, new_path_to, golden_file_path,eachimage.split(".")[0]+"_"+"registered.nii.gz", create_subfolder=False) + + + # enchance and normalize image + #if not os.path.exists(new_path_to+"/no_z"): + # os.mkdir(new_path_to+"/no_z") + + image_sitk = sitk.ReadImage(os.path.join(new_path_to, eachimage.split(".")[0]+"_"+"registered.nii.gz")) + image_array = sitk.GetArrayFromImage(image_sitk) + image_array = enhance(image_array) # or enhance_noN4(image_array) if no bias field correction is needed + image3 = sitk.GetImageFromArray(image_array) + sitk.WriteImage(image3,os.path.join(new_path_to, eachimage.split(".")[0]+"_"+"registered_no_z.nii.gz")) + + #skull strip ## when running this with rest of the preprocessing, change the src path to include the registered image path!!!! + new_path_to = path_to + run_hd_bet(os.path.join(new_path_to, eachimage.split(".")[0]+"_"+"registered_no_z.nii.gz"),os.path.join(new_path_to, eachimage), + mode="accurate", + config_file='/media/sdb/divyanshu/divyanshu/aidan_segmentation/pediatric-brain-age-main/HD_BET/config.py', + device=device, + postprocess=False, + do_tta=True, + keep_mask=True, + overwrite=True) + diff --git a/src/BrainIAC/load_brainiac.py b/src/BrainIAC/load_brainiac.py new file mode 100644 index 0000000000000000000000000000000000000000..230c316a89557f39d1ed376bddf261d44a8d8610 --- /dev/null +++ b/src/BrainIAC/load_brainiac.py @@ -0,0 +1,39 @@ +import torch +from model import ResNet50_3D +import argparse + +def load_brainiac(checkpoint_path, device='cuda'): + """ + Load the ResNet50 model and BrainIAC checkpoint. + + Args: + checkpoint_path (str): Path to the model checkpoint + device (str): Device to load the model on ('cuda' or 'cpu') + + Returns: + model: Loaded model with checkpoint weights + """ + # spinup the model + model = ResNet50_3D() + + # Load brainiac weights + checkpoint = torch.load(checkpoint_path, map_location=device) + state_dict = checkpoint["state_dict"] + filtered_state_dict = {key: value for key, value in state_dict.items() if 'backbone' in key} + model.load_state_dict(filtered_state_dict) + print("BrainIAC Loaded!!") + + return model + +if __name__ == "__main__": + # Parse args + parser = argparse.ArgumentParser(description='Load backbone model from checkpoint') + parser.add_argument('--checkpoint', type=str, required=True, + help='Path to the model checkpoint') + parser.add_argument('--device', type=str, default='cuda', + help='Device to load the model on (cuda or cpu)') + args = parser.parse_args() + + # Load model + model = load_brainiac(args.checkpoint, args.device) + print(f"Model loaded successfully from {args.checkpoint}!") \ No newline at end of file diff --git a/src/BrainIAC/model.py b/src/BrainIAC/model.py new file mode 100644 index 0000000000000000000000000000000000000000..92bf8b87cb7e5678ae4f79a430ffc588a26ec271 --- /dev/null +++ b/src/BrainIAC/model.py @@ -0,0 +1,85 @@ +import torch.nn as nn +from monai.networks.nets import resnet101, resnet50, resnet18, ViT +import torch + + + +## resnet50 architecture, FC layers converted to I +class ResNet50_3D(nn.Module): + def __init__(self): + super(ResNet50_3D, self).__init__() + + resnet = resnet50(pretrained=False) # assuming you're not using a pretrained model + resnet.conv1 = nn.Conv3d(1, 64, kernel_size=7, stride=2, padding=3, bias=False) + hidden_dim = resnet.fc.in_features + self.backbone = resnet + self.backbone.fc = nn.Identity() + + def forward(self, x): + x = self.backbone(x) + return x + + + +class Classifier(nn.Module): + """ Classifier class with FC layer and single output neuron """ + def __init__(self, d_model, hidden_dim=1024, num_classes=1): + super(Classifier, self).__init__() + self.fc = nn.Linear(d_model, num_classes) + def forward(self, x): + x = self.fc(x) + return x + + +class Backbone(nn.Module): + """ ResNet 3D Backbone""" + + def __init__(self): + super(Backbone, self).__init__() + + resnet = resnet50(pretrained=False) # assuming you're not using a pretrained model + resnet.conv1 = nn.Conv3d(1, 64, kernel_size=7, stride=2, padding=3, bias=False) + hidden_dim = resnet.fc.in_features + self.backbone = resnet + self.backbone.fc = nn.Identity() + + def forward(self, x): + x = self.backbone(x) + return x + + +class SingleScanModel(nn.Module): + """ End to end model with backbone and classifier""" + + def __init__(self, backbone, classifier): + super(SingleScanModel, self).__init__() + self.backbone = backbone + self.classifier = classifier + self.dropout = nn.Dropout(p=0.2) + + + def forward(self, x): + + x = self.backbone(x) + x = self.dropout(x) + x = self.classifier(x) + return x + +class SingleScanModelBP(nn.Module): + """ End to end model with backbone and classifier that takes 2 input scans at once""" + + def __init__(self, backbone, classifier): + super(SingleScanModelBP, self).__init__() + self.backbone = backbone + self.classifier = classifier + self.dropout = nn.Dropout(p=0.2) + self.bilinear_pooling = nn.Bilinear(in1_features=2048, in2_features=2048, out_features=512) + + + def forward(self, x): + x = [self.backbone(scan) for scan in x.split(1, dim=1)] + features = torch.stack(x, dim=1).squeeze(2) + merged_features = torch.mean(features, dim=1) # Shape: (batch_size, feature_dim) + merged_features = self.dropout(merged_features) + output = self.classifier(merged_features) + return output \ No newline at end of file diff --git a/src/BrainIAC/preprocessing/HDBET_Code/.DS_Store b/src/BrainIAC/preprocessing/HDBET_Code/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cde915ad96f102ec333b6157854d8779e38406d1 Binary files /dev/null and b/src/BrainIAC/preprocessing/HDBET_Code/.DS_Store differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/config.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a995f59b6f85eabc025629df1c500b698f67bb5 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/config.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/data_loading.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/data_loading.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90c38b57865fe2c9e2384f17a6136fd5c7f9a991 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/data_loading.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/hd_bet.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/hd_bet.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0f828a3117d36e2f89037ed3d029d9fe39864e8 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/hd_bet.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/network_architecture.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/network_architecture.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b844d1733cc804934064353aaa69edc523bab254 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/network_architecture.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/paths.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/paths.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18dd04e17027dd282fc5ece3396f18c13eb6e85d Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/paths.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/predict_case.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/predict_case.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0594e090eac89fb59c9583820c6562556255637 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/predict_case.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/run.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/run.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77abae02891cd3f7e1bd7673081f294f0db2054f Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/run.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/__pycache__/utils.cpython-39.pyc b/src/BrainIAC/preprocessing/HD_BET/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0e84846be112dcb4b7e3c7b2c6205656747a0b9 Binary files /dev/null and b/src/BrainIAC/preprocessing/HD_BET/__pycache__/utils.cpython-39.pyc differ diff --git a/src/BrainIAC/preprocessing/HD_BET/config.py b/src/BrainIAC/preprocessing/HD_BET/config.py new file mode 100644 index 0000000000000000000000000000000000000000..870951e5c9059fb9e20d6143e68266732f19234e --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/config.py @@ -0,0 +1,121 @@ +import numpy as np +import torch +from HD_BET.utils import SetNetworkToVal, softmax_helper +from abc import abstractmethod +from HD_BET.network_architecture import Network + + +class BaseConfig(object): + def __init__(self): + pass + + @abstractmethod + def get_split(self, fold, random_state=12345): + pass + + @abstractmethod + def get_network(self, mode="train"): + pass + + @abstractmethod + def get_basic_generators(self, fold): + pass + + @abstractmethod + def get_data_generators(self, fold): + pass + + def preprocess(self, data): + return data + + def __repr__(self): + res = "" + for v in vars(self): + if not v.startswith("__") and not v.startswith("_") and v != 'dataset': + res += (v + ": " + str(self.__getattribute__(v)) + "\n") + return res + + +class HD_BET_Config(BaseConfig): + def __init__(self): + super(HD_BET_Config, self).__init__() + + self.EXPERIMENT_NAME = self.__class__.__name__ # just a generic experiment name + + # network parameters + self.net_base_num_layers = 21 + self.BATCH_SIZE = 2 + self.net_do_DS = True + self.net_dropout_p = 0.0 + self.net_use_inst_norm = True + self.net_conv_use_bias = True + self.net_norm_use_affine = True + self.net_leaky_relu_slope = 1e-1 + + # hyperparameters + self.INPUT_PATCH_SIZE = (128, 128, 128) + self.num_classes = 2 + self.selected_data_channels = range(1) + + # data augmentation + self.da_mirror_axes = (2, 3, 4) + + # validation + self.val_use_DO = False + self.val_use_train_mode = False # for dropout sampling + self.val_num_repeats = 1 # only useful if dropout sampling + self.val_batch_size = 1 # only useful if dropout sampling + self.val_save_npz = True + self.val_do_mirroring = True # test time data augmentation via mirroring + self.val_write_images = True + self.net_input_must_be_divisible_by = 16 # we could make a network class that has this as a property + self.val_min_size = self.INPUT_PATCH_SIZE + self.val_fn = None + + # CAREFUL! THIS IS A HACK TO MAKE PYTORCH 0.3 STATE DICTS COMPATIBLE WITH PYTORCH 0.4 (setting keep_runnings_ + # stats=True but not using them in validation. keep_runnings_stats was True before 0.3 but unused and defaults + # to false in 0.4) + self.val_use_moving_averages = False + + def get_network(self, train=True, pretrained_weights=None): + net = Network(self.num_classes, len(self.selected_data_channels), self.net_base_num_layers, + self.net_dropout_p, softmax_helper, self.net_leaky_relu_slope, self.net_conv_use_bias, + self.net_norm_use_affine, True, self.net_do_DS) + + if pretrained_weights is not None: + net.load_state_dict( + torch.load(pretrained_weights, map_location=lambda storage, loc: storage)) + + if train: + net.train(True) + else: + net.train(False) + net.apply(SetNetworkToVal(self.val_use_DO, self.val_use_moving_averages)) + net.do_ds = False + + optimizer = None + self.lr_scheduler = None + return net, optimizer + + def get_data_generators(self, fold): + pass + + def get_split(self, fold, random_state=12345): + pass + + def get_basic_generators(self, fold): + pass + + def on_epoch_end(self, epoch): + pass + + def preprocess(self, data): + data = np.copy(data) + for c in range(data.shape[0]): + data[c] -= data[c].mean() + data[c] /= data[c].std() + return data + + +config = HD_BET_Config + diff --git a/src/BrainIAC/preprocessing/HD_BET/data_loading.py b/src/BrainIAC/preprocessing/HD_BET/data_loading.py new file mode 100644 index 0000000000000000000000000000000000000000..8ec4be63a8186b65bfb390770fefa6217b5dd2c5 --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/data_loading.py @@ -0,0 +1,121 @@ +import SimpleITK as sitk +import numpy as np +from skimage.transform import resize + + +def resize_image(image, old_spacing, new_spacing, order=3): + new_shape = (int(np.round(old_spacing[0]/new_spacing[0]*float(image.shape[0]))), + int(np.round(old_spacing[1]/new_spacing[1]*float(image.shape[1]))), + int(np.round(old_spacing[2]/new_spacing[2]*float(image.shape[2])))) + return resize(image, new_shape, order=order, mode='edge', cval=0, anti_aliasing=False) + + +def preprocess_image(itk_image, is_seg=False, spacing_target=(1, 0.5, 0.5)): + spacing = np.array(itk_image.GetSpacing())[[2, 1, 0]] + image = sitk.GetArrayFromImage(itk_image).astype(float) + + assert len(image.shape) == 3, "The image has unsupported number of dimensions. Only 3D images are allowed" + + if not is_seg: + if np.any([[i != j] for i, j in zip(spacing, spacing_target)]): + image = resize_image(image, spacing, spacing_target).astype(np.float32) + + image -= image.mean() + image /= image.std() + else: + new_shape = (int(np.round(spacing[0] / spacing_target[0] * float(image.shape[0]))), + int(np.round(spacing[1] / spacing_target[1] * float(image.shape[1]))), + int(np.round(spacing[2] / spacing_target[2] * float(image.shape[2])))) + image = resize_segmentation(image, new_shape, 1) + return image + + +def load_and_preprocess(mri_file): + images = {} + # t1 + images["T1"] = sitk.ReadImage(mri_file) + + properties_dict = { + "spacing": images["T1"].GetSpacing(), + "direction": images["T1"].GetDirection(), + "size": images["T1"].GetSize(), + "origin": images["T1"].GetOrigin() + } + + for k in images.keys(): + images[k] = preprocess_image(images[k], is_seg=False, spacing_target=(1.5, 1.5, 1.5)) + + properties_dict['size_before_cropping'] = images["T1"].shape + + imgs = [] + for seq in ['T1']: + imgs.append(images[seq][None]) + all_data = np.vstack(imgs) + print("image shape after preprocessing: ", str(all_data[0].shape)) + return all_data, properties_dict + + +def save_segmentation_nifti(segmentation, dct, out_fname, order=1): + ''' + segmentation must have the same spacing as the original nifti (for now). segmentation may have been cropped out + of the original image + + dct: + size_before_cropping + brain_bbox + size -> this is the original size of the dataset, if the image was not resampled, this is the same as size_before_cropping + spacing + origin + direction + + :param segmentation: + :param dct: + :param out_fname: + :return: + ''' + old_size = dct.get('size_before_cropping') + bbox = dct.get('brain_bbox') + if bbox is not None: + seg_old_size = np.zeros(old_size) + for c in range(3): + bbox[c][1] = np.min((bbox[c][0] + segmentation.shape[c], old_size[c])) + seg_old_size[bbox[0][0]:bbox[0][1], + bbox[1][0]:bbox[1][1], + bbox[2][0]:bbox[2][1]] = segmentation + else: + seg_old_size = segmentation + if np.any(np.array(seg_old_size) != np.array(dct['size'])[[2, 1, 0]]): + seg_old_spacing = resize_segmentation(seg_old_size, np.array(dct['size'])[[2, 1, 0]], order=order) + else: + seg_old_spacing = seg_old_size + seg_resized_itk = sitk.GetImageFromArray(seg_old_spacing.astype(np.int32)) + seg_resized_itk.SetSpacing(np.array(dct['spacing'])[[0, 1, 2]]) + seg_resized_itk.SetOrigin(dct['origin']) + seg_resized_itk.SetDirection(dct['direction']) + sitk.WriteImage(seg_resized_itk, out_fname) + + +def resize_segmentation(segmentation, new_shape, order=3, cval=0): + ''' + Taken from batchgenerators (https://github.com/MIC-DKFZ/batchgenerators) to prevent dependency + + Resizes a segmentation map. Supports all orders (see skimage documentation). Will transform segmentation map to one + hot encoding which is resized and transformed back to a segmentation map. + This prevents interpolation artifacts ([0, 0, 2] -> [0, 1, 2]) + :param segmentation: + :param new_shape: + :param order: + :return: + ''' + tpe = segmentation.dtype + unique_labels = np.unique(segmentation) + assert len(segmentation.shape) == len(new_shape), "new shape must have same dimensionality as segmentation" + if order == 0: + return resize(segmentation, new_shape, order, mode="constant", cval=cval, clip=True, anti_aliasing=False).astype(tpe) + else: + reshaped = np.zeros(new_shape, dtype=segmentation.dtype) + + for i, c in enumerate(unique_labels): + reshaped_multihot = resize((segmentation == c).astype(float), new_shape, order, mode="edge", clip=True, anti_aliasing=False) + reshaped[reshaped_multihot >= 0.5] = c + return reshaped diff --git a/src/BrainIAC/preprocessing/HD_BET/hd_bet.py b/src/BrainIAC/preprocessing/HD_BET/hd_bet.py new file mode 100644 index 0000000000000000000000000000000000000000..128575b6cfb4bdd98bf417ed598f905ef4896fd1 --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/hd_bet.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.append("/mnt/93E8-0534/AIDAN/HDBET/") +from HD_BET.run import run_hd_bet +from HD_BET.utils import maybe_mkdir_p, subfiles +import HD_BET + +def hd_bet(input_file_or_dir,output_file_or_dir,mode,device,tta,pp=1,save_mask=0,overwrite_existing=1): + + if output_file_or_dir is None: + output_file_or_dir = os.path.join(os.path.dirname(input_file_or_dir), + os.path.basename(input_file_or_dir).split(".")[0] + "_bet") + + + params_file = os.path.join(HD_BET.__path__[0], "model_final.py") + config_file = os.path.join(HD_BET.__path__[0], "config.py") + + assert os.path.abspath(input_file_or_dir) != os.path.abspath(output_file_or_dir), "output must be different from input" + + if device == 'cpu': + pass + else: + device = int(device) + + if os.path.isdir(input_file_or_dir): + maybe_mkdir_p(output_file_or_dir) + input_files = subfiles(input_file_or_dir, suffix='_0000.nii.gz', join=False) + + if len(input_files) == 0: + raise RuntimeError("input is a folder but no nifti files (.nii.gz) were found in here") + + output_files = [os.path.join(output_file_or_dir, i) for i in input_files] + input_files = [os.path.join(input_file_or_dir, i) for i in input_files] + else: + if not output_file_or_dir.endswith('.nii.gz'): + output_file_or_dir += '.nii.gz' + assert os.path.abspath(input_file_or_dir) != os.path.abspath(output_file_or_dir), "output must be different from input" + + output_files = [output_file_or_dir] + input_files = [input_file_or_dir] + + if tta == 0: + tta = False + elif tta == 1: + tta = True + else: + raise ValueError("Unknown value for tta: %s. Expected: 0 or 1" % str(tta)) + + if overwrite_existing == 0: + overwrite_existing = False + elif overwrite_existing == 1: + overwrite_existing = True + else: + raise ValueError("Unknown value for overwrite_existing: %s. Expected: 0 or 1" % str(overwrite_existing)) + + if pp == 0: + pp = False + elif pp == 1: + pp = True + else: + raise ValueError("Unknown value for pp: %s. Expected: 0 or 1" % str(pp)) + + if save_mask == 0: + save_mask = False + elif save_mask == 1: + save_mask = True + else: + raise ValueError("Unknown value for pp: %s. Expected: 0 or 1" % str(pp)) + + run_hd_bet(input_files, output_files, mode, config_file, device, pp, tta, save_mask, overwrite_existing) + + +if __name__ == "__main__": + print("\n########################") + print("If you are using hd-bet, please cite the following paper:") + print("Isensee F, Schell M, Tursunova I, Brugnara G, Bonekamp D, Neuberger U, Wick A, Schlemmer HP, Heiland S, Wick W," + "Bendszus M, Maier-Hein KH, Kickingereder P. Automated brain extraction of multi-sequence MRI using artificial" + "neural networks. arXiv preprint arXiv:1901.11341, 2019.") + print("########################\n") + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--input', help='input. Can be either a single file name or an input folder. If file: must be ' + 'nifti (.nii.gz) and can only be 3D. No support for 4d images, use fslsplit to ' + 'split 4d sequences into 3d images. If folder: all files ending with .nii.gz ' + 'within that folder will be brain extracted.', required=True, type=str) + parser.add_argument('-o', '--output', help='output. Can be either a filename or a folder. If it does not exist, the folder' + ' will be created', required=False, type=str) + parser.add_argument('-mode', type=str, default='accurate', help='can be either \'fast\' or \'accurate\'. Fast will ' + 'use only one set of parameters whereas accurate will ' + 'use the five sets of parameters that resulted from ' + 'our cross-validation as an ensemble. Default: ' + 'accurate', + required=False) + parser.add_argument('-device', default='0', type=str, help='used to set on which device the prediction will run. ' + 'Must be either int or str. Use int for GPU id or ' + '\'cpu\' to run on CPU. When using CPU you should ' + 'consider disabling tta. Default for -device is: 0', + required=False) + parser.add_argument('-tta', default=1, required=False, type=int, help='whether to use test time data augmentation ' + '(mirroring). 1= True, 0=False. Disable this ' + 'if you are using CPU to speed things up! ' + 'Default: 1') + parser.add_argument('-pp', default=1, type=int, required=False, help='set to 0 to disabe postprocessing (remove all' + ' but the largest connected component in ' + 'the prediction. Default: 1') + parser.add_argument('-s', '--save_mask', default=1, type=int, required=False, help='if set to 0 the segmentation ' + 'mask will not be ' + 'saved') + parser.add_argument('--overwrite_existing', default=1, type=int, required=False, help="set this to 0 if you don't " + "want to overwrite existing " + "predictions") + + args = parser.parse_args() + + hd_bet(args.input,args.output,args.mode,args.device,args.tta,args.pp,args.save_mask,args.overwrite_existing) + diff --git a/src/BrainIAC/preprocessing/HD_BET/network_architecture.py b/src/BrainIAC/preprocessing/HD_BET/network_architecture.py new file mode 100644 index 0000000000000000000000000000000000000000..0824aa10839024368ad8ab38c637ce81aa9327e5 --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/network_architecture.py @@ -0,0 +1,213 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from HD_BET.utils import softmax_helper + + +class EncodingModule(nn.Module): + def __init__(self, in_channels, out_channels, filter_size=3, dropout_p=0.3, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True): + nn.Module.__init__(self) + self.dropout_p = dropout_p + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.bn_1 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv1 = nn.Conv3d(in_channels, out_channels, filter_size, 1, (filter_size - 1) // 2, bias=self.conv_bias) + self.dropout = nn.Dropout3d(dropout_p) + self.bn_2 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv2 = nn.Conv3d(out_channels, out_channels, filter_size, 1, (filter_size - 1) // 2, bias=self.conv_bias) + + def forward(self, x): + skip = x + x = F.leaky_relu(self.bn_1(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.conv1(x) + if self.dropout_p is not None and self.dropout_p > 0: + x = self.dropout(x) + x = F.leaky_relu(self.bn_2(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.conv2(x) + x = x + skip + return x + + +class Upsample(nn.Module): + def __init__(self, size=None, scale_factor=None, mode='nearest', align_corners=True): + super(Upsample, self).__init__() + self.align_corners = align_corners + self.mode = mode + self.scale_factor = scale_factor + self.size = size + + def forward(self, x): + return nn.functional.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode, + align_corners=self.align_corners) + + +class LocalizationModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.conv1 = nn.Conv3d(in_channels, in_channels, 3, 1, 1, bias=self.conv_bias) + self.bn_1 = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.conv2 = nn.Conv3d(in_channels, out_channels, 1, 1, 0, bias=self.conv_bias) + self.bn_2 = nn.InstanceNorm3d(out_channels, affine=self.inst_norm_affine, track_running_stats=True) + + def forward(self, x): + x = F.leaky_relu(self.bn_1(self.conv1(x)), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = F.leaky_relu(self.bn_2(self.conv2(x)), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + return x + + +class UpsamplingModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.upsample = Upsample(scale_factor=2, mode="trilinear", align_corners=True) + self.upsample_conv = nn.Conv3d(in_channels, out_channels, 3, 1, 1, bias=self.conv_bias) + self.bn = nn.InstanceNorm3d(out_channels, affine=self.inst_norm_affine, track_running_stats=True) + + def forward(self, x): + x = F.leaky_relu(self.bn(self.upsample_conv(self.upsample(x))), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + return x + + +class DownsamplingModule(nn.Module): + def __init__(self, in_channels, out_channels, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True): + nn.Module.__init__(self) + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.bn = nn.InstanceNorm3d(in_channels, affine=self.inst_norm_affine, track_running_stats=True) + self.downsample = nn.Conv3d(in_channels, out_channels, 3, 2, 1, bias=self.conv_bias) + + def forward(self, x): + x = F.leaky_relu(self.bn(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + b = self.downsample(x) + return x, b + + +class Network(nn.Module): + def __init__(self, num_classes=4, num_input_channels=4, base_filters=16, dropout_p=0.3, + final_nonlin=softmax_helper, leakiness=1e-2, conv_bias=True, inst_norm_affine=True, + lrelu_inplace=True, do_ds=True): + super(Network, self).__init__() + + self.do_ds = do_ds + self.lrelu_inplace = lrelu_inplace + self.inst_norm_affine = inst_norm_affine + self.conv_bias = conv_bias + self.leakiness = leakiness + self.final_nonlin = final_nonlin + self.init_conv = nn.Conv3d(num_input_channels, base_filters, 3, 1, 1, bias=self.conv_bias) + + self.context1 = EncodingModule(base_filters, base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down1 = DownsamplingModule(base_filters, base_filters * 2, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context2 = EncodingModule(2 * base_filters, 2 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down2 = DownsamplingModule(2 * base_filters, base_filters * 4, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context3 = EncodingModule(4 * base_filters, 4 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down3 = DownsamplingModule(4 * base_filters, base_filters * 8, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context4 = EncodingModule(8 * base_filters, 8 * base_filters, 3, dropout_p, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.down4 = DownsamplingModule(8 * base_filters, base_filters * 16, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.context5 = EncodingModule(16 * base_filters, 16 * base_filters, 3, dropout_p, leakiness=1e-2, + conv_bias=True, inst_norm_affine=True, lrelu_inplace=True) + + self.bn_after_context5 = nn.InstanceNorm3d(16 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.up1 = UpsamplingModule(16 * base_filters, 8 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc1 = LocalizationModule(16 * base_filters, 8 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.up2 = UpsamplingModule(8 * base_filters, 4 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc2 = LocalizationModule(8 * base_filters, 4 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.loc2_seg = nn.Conv3d(4 * base_filters, num_classes, 1, 1, 0, bias=False) + self.up3 = UpsamplingModule(4 * base_filters, 2 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.loc3 = LocalizationModule(4 * base_filters, 2 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + self.loc3_seg = nn.Conv3d(2 * base_filters, num_classes, 1, 1, 0, bias=False) + self.up4 = UpsamplingModule(2 * base_filters, 1 * base_filters, leakiness=1e-2, conv_bias=True, + inst_norm_affine=True, lrelu_inplace=True) + + self.end_conv_1 = nn.Conv3d(2 * base_filters, 2 * base_filters, 3, 1, 1, bias=self.conv_bias) + self.end_conv_1_bn = nn.InstanceNorm3d(2 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.end_conv_2 = nn.Conv3d(2 * base_filters, 2 * base_filters, 3, 1, 1, bias=self.conv_bias) + self.end_conv_2_bn = nn.InstanceNorm3d(2 * base_filters, affine=self.inst_norm_affine, track_running_stats=True) + self.seg_layer = nn.Conv3d(2 * base_filters, num_classes, 1, 1, 0, bias=False) + + def forward(self, x): + seg_outputs = [] + + x = self.init_conv(x) + x = self.context1(x) + + skip1, x = self.down1(x) + x = self.context2(x) + + skip2, x = self.down2(x) + x = self.context3(x) + + skip3, x = self.down3(x) + x = self.context4(x) + + skip4, x = self.down4(x) + x = self.context5(x) + + x = F.leaky_relu(self.bn_after_context5(x), negative_slope=self.leakiness, inplace=self.lrelu_inplace) + x = self.up1(x) + + x = torch.cat((skip4, x), dim=1) + x = self.loc1(x) + x = self.up2(x) + + x = torch.cat((skip3, x), dim=1) + x = self.loc2(x) + loc2_seg = self.final_nonlin(self.loc2_seg(x)) + seg_outputs.append(loc2_seg) + x = self.up3(x) + + x = torch.cat((skip2, x), dim=1) + x = self.loc3(x) + loc3_seg = self.final_nonlin(self.loc3_seg(x)) + seg_outputs.append(loc3_seg) + x = self.up4(x) + + x = torch.cat((skip1, x), dim=1) + x = F.leaky_relu(self.end_conv_1_bn(self.end_conv_1(x)), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + x = F.leaky_relu(self.end_conv_2_bn(self.end_conv_2(x)), negative_slope=self.leakiness, + inplace=self.lrelu_inplace) + x = self.final_nonlin(self.seg_layer(x)) + seg_outputs.append(x) + + if self.do_ds: + return seg_outputs[::-1] + else: + return seg_outputs[-1] diff --git a/src/BrainIAC/preprocessing/HD_BET/paths.py b/src/BrainIAC/preprocessing/HD_BET/paths.py new file mode 100644 index 0000000000000000000000000000000000000000..e78cad195250591329a897705a1b1f58ec9a60ed --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/paths.py @@ -0,0 +1,6 @@ +import os + +# please refer to the readme on where to get the parameters. Save them in this folder: +current_dir = os.path.dirname(os.path.abspath(__file__)) +preprocessing_dir = os.path.dirname(current_dir) +folder_with_parameter_files = os.path.join(preprocessing_dir, 'hd-bet_params') diff --git a/src/BrainIAC/preprocessing/HD_BET/predict_case.py b/src/BrainIAC/preprocessing/HD_BET/predict_case.py new file mode 100644 index 0000000000000000000000000000000000000000..559c66739ae890f7e985e072eb49ce0ee0484978 --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/predict_case.py @@ -0,0 +1,126 @@ +import torch +import numpy as np + + +def pad_patient_3D(patient, shape_must_be_divisible_by=16, min_size=None): + if not (isinstance(shape_must_be_divisible_by, list) or isinstance(shape_must_be_divisible_by, tuple)): + shape_must_be_divisible_by = [shape_must_be_divisible_by] * 3 + shp = patient.shape + new_shp = [shp[0] + shape_must_be_divisible_by[0] - shp[0] % shape_must_be_divisible_by[0], + shp[1] + shape_must_be_divisible_by[1] - shp[1] % shape_must_be_divisible_by[1], + shp[2] + shape_must_be_divisible_by[2] - shp[2] % shape_must_be_divisible_by[2]] + for i in range(len(shp)): + if shp[i] % shape_must_be_divisible_by[i] == 0: + new_shp[i] -= shape_must_be_divisible_by[i] + if min_size is not None: + new_shp = np.max(np.vstack((np.array(new_shp), np.array(min_size))), 0) + return reshape_by_padding_upper_coords(patient, new_shp, 0), shp + + +def reshape_by_padding_upper_coords(image, new_shape, pad_value=None): + shape = tuple(list(image.shape)) + new_shape = tuple(np.max(np.concatenate((shape, new_shape)).reshape((2,len(shape))), axis=0)) + if pad_value is None: + if len(shape) == 2: + pad_value = image[0,0] + elif len(shape) == 3: + pad_value = image[0, 0, 0] + else: + raise ValueError("Image must be either 2 or 3 dimensional") + res = np.ones(list(new_shape), dtype=image.dtype) * pad_value + if len(shape) == 2: + res[0:0+int(shape[0]), 0:0+int(shape[1])] = image + elif len(shape) == 3: + res[0:0+int(shape[0]), 0:0+int(shape[1]), 0:0+int(shape[2])] = image + return res + + +def predict_case_3D_net(net, patient_data, do_mirroring, num_repeats, BATCH_SIZE=None, + new_shape_must_be_divisible_by=16, min_size=None, main_device=0, mirror_axes=(2, 3, 4)): + with torch.no_grad(): + pad_res = [] + for i in range(patient_data.shape[0]): + t, old_shape = pad_patient_3D(patient_data[i], new_shape_must_be_divisible_by, min_size) + pad_res.append(t[None]) + + patient_data = np.vstack(pad_res) + + new_shp = patient_data.shape + + data = np.zeros(tuple([1] + list(new_shp)), dtype=np.float32) + + data[0] = patient_data + + if BATCH_SIZE is not None: + data = np.vstack([data] * BATCH_SIZE) + + a = torch.rand(data.shape).float() + + if main_device == 'cpu': + pass + else: + a = a.cuda(main_device) + + if do_mirroring: + x = 8 + else: + x = 1 + all_preds = [] + for i in range(num_repeats): + for m in range(x): + data_for_net = np.array(data) + do_stuff = False + if m == 0: + do_stuff = True + pass + if m == 1 and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, :, ::-1] + if m == 2 and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, ::-1, :] + if m == 3 and (4 in mirror_axes) and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, :, ::-1, ::-1] + if m == 4 and (2 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, :, :] + if m == 5 and (2 in mirror_axes) and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, :, ::-1] + if m == 6 and (2 in mirror_axes) and (3 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, ::-1, :] + if m == 7 and (2 in mirror_axes) and (3 in mirror_axes) and (4 in mirror_axes): + do_stuff = True + data_for_net = data_for_net[:, :, ::-1, ::-1, ::-1] + + if do_stuff: + _ = a.data.copy_(torch.from_numpy(np.copy(data_for_net))) + p = net(a) # np.copy is necessary because ::-1 creates just a view i think + p = p.data.cpu().numpy() + + if m == 0: + pass + if m == 1 and (4 in mirror_axes): + p = p[:, :, :, :, ::-1] + if m == 2 and (3 in mirror_axes): + p = p[:, :, :, ::-1, :] + if m == 3 and (4 in mirror_axes) and (3 in mirror_axes): + p = p[:, :, :, ::-1, ::-1] + if m == 4 and (2 in mirror_axes): + p = p[:, :, ::-1, :, :] + if m == 5 and (2 in mirror_axes) and (4 in mirror_axes): + p = p[:, :, ::-1, :, ::-1] + if m == 6 and (2 in mirror_axes) and (3 in mirror_axes): + p = p[:, :, ::-1, ::-1, :] + if m == 7 and (2 in mirror_axes) and (3 in mirror_axes) and (4 in mirror_axes): + p = p[:, :, ::-1, ::-1, ::-1] + all_preds.append(p) + + stacked = np.vstack(all_preds)[:, :, :old_shape[0], :old_shape[1], :old_shape[2]] + predicted_segmentation = stacked.mean(0).argmax(0) + uncertainty = stacked.var(0) + bayesian_predictions = stacked + softmax_pred = stacked.mean(0) + return predicted_segmentation, bayesian_predictions, softmax_pred, uncertainty diff --git a/src/BrainIAC/preprocessing/HD_BET/run.py b/src/BrainIAC/preprocessing/HD_BET/run.py new file mode 100644 index 0000000000000000000000000000000000000000..858934d8f67175df508884e9030f8d38ba0d07cf --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/run.py @@ -0,0 +1,117 @@ +import torch +import numpy as np +import SimpleITK as sitk +from HD_BET.data_loading import load_and_preprocess, save_segmentation_nifti +from HD_BET.predict_case import predict_case_3D_net +import imp +from HD_BET.utils import postprocess_prediction, SetNetworkToVal, get_params_fname, maybe_download_parameters +import os +import HD_BET + + +def apply_bet(img, bet, out_fname): + img_itk = sitk.ReadImage(img) + img_npy = sitk.GetArrayFromImage(img_itk) + img_bet = sitk.GetArrayFromImage(sitk.ReadImage(bet)) + img_npy[img_bet == 0] = 0 + out = sitk.GetImageFromArray(img_npy) + out.CopyInformation(img_itk) + sitk.WriteImage(out, out_fname) + + +def run_hd_bet(mri_fnames, output_fnames, mode="accurate", config_file=os.path.join(HD_BET.__path__[0], "config.py"), device=0, + postprocess=False, do_tta=True, keep_mask=True, overwrite=True): + """ + + :param mri_fnames: str or list/tuple of str + :param output_fnames: str or list/tuple of str. If list: must have the same length as output_fnames + :param mode: fast or accurate + :param config_file: config.py + :param device: either int (for device id) or 'cpu' + :param postprocess: whether to do postprocessing or not. Postprocessing here consists of simply discarding all + but the largest predicted connected component. Default False + :param do_tta: whether to do test time data augmentation by mirroring along all axes. Default: True. If you use + CPU you may want to turn that off to speed things up + :return: + """ + + list_of_param_files = [] + + if mode == 'fast': + params_file = get_params_fname(0) + maybe_download_parameters(0) + + list_of_param_files.append(params_file) + elif mode == 'accurate': + for i in range(5): + params_file = get_params_fname(i) + maybe_download_parameters(i) + + list_of_param_files.append(params_file) + else: + raise ValueError("Unknown value for mode: %s. Expected: fast or accurate" % mode) + + assert all([os.path.isfile(i) for i in list_of_param_files]), "Could not find parameter files" + + cf = imp.load_source('cf', config_file) + cf = cf.config() + + net, _ = cf.get_network(cf.val_use_train_mode, None) + if device == "cpu": + net = net.cpu() + else: + net.cuda(device) + + if not isinstance(mri_fnames, (list, tuple)): + mri_fnames = [mri_fnames] + + if not isinstance(output_fnames, (list, tuple)): + output_fnames = [output_fnames] + + assert len(mri_fnames) == len(output_fnames), "mri_fnames and output_fnames must have the same length" + + params = [] + for p in list_of_param_files: + params.append(torch.load(p, map_location=lambda storage, loc: storage)) + + for in_fname, out_fname in zip(mri_fnames, output_fnames): + mask_fname = out_fname[:-7] + "_mask.nii.gz" + if overwrite or (not (os.path.isfile(mask_fname) and keep_mask) or not os.path.isfile(out_fname)): + print("File:", in_fname) + print("preprocessing...") + try: + data, data_dict = load_and_preprocess(in_fname) + except RuntimeError: + print("\nERROR\nCould not read file", in_fname, "\n") + continue + except AssertionError as e: + print(e) + continue + + softmax_preds = [] + + print("prediction (CNN id)...") + for i, p in enumerate(params): + print(i) + net.load_state_dict(p) + net.eval() + net.apply(SetNetworkToVal(False, False)) + _, _, softmax_pred, _ = predict_case_3D_net(net, data, do_tta, cf.val_num_repeats, + cf.val_batch_size, cf.net_input_must_be_divisible_by, + cf.val_min_size, device, cf.da_mirror_axes) + softmax_preds.append(softmax_pred[None]) + + seg = np.argmax(np.vstack(softmax_preds).mean(0), 0) + + if postprocess: + seg = postprocess_prediction(seg) + + print("exporting segmentation...") + save_segmentation_nifti(seg, data_dict, mask_fname) + + apply_bet(in_fname, mask_fname, out_fname) + + if not keep_mask: + os.remove(mask_fname) + + diff --git a/src/BrainIAC/preprocessing/HD_BET/utils.py b/src/BrainIAC/preprocessing/HD_BET/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..3ba72a3d4d70accfd1fdc313a2f80b8d4c4c6eea --- /dev/null +++ b/src/BrainIAC/preprocessing/HD_BET/utils.py @@ -0,0 +1,115 @@ +from urllib.request import urlopen +import torch +from torch import nn +import numpy as np +from skimage.morphology import label +import os +from HD_BET.paths import folder_with_parameter_files + + +def get_params_fname(fold): + return os.path.join(folder_with_parameter_files, "%d.model" % fold) + + +def maybe_download_parameters(fold=0, force_overwrite=False): + """ + Downloads the parameters for some fold if it is not present yet. + :param fold: + :param force_overwrite: if True the old parameter file will be deleted (if present) prior to download + :return: + """ + + assert 0 <= fold <= 4, "fold must be between 0 and 4" + + if not os.path.isdir(folder_with_parameter_files): + maybe_mkdir_p(folder_with_parameter_files) + + out_filename = get_params_fname(fold) + + if force_overwrite and os.path.isfile(out_filename): + os.remove(out_filename) + + if not os.path.isfile(out_filename): + url = "https://zenodo.org/record/2540695/files/%d.model?download=1" % fold + print("Downloading", url, "...") + data = urlopen(url).read() + #out_filename = "/media/sdb/divyanshu/divyanshu/aidan_segmentation/nnUNet_pLGG/home/divyanshu/hd-bet_params/0.model" + with open(out_filename, 'wb') as f: + f.write(data) + + +def init_weights(module): + if isinstance(module, nn.Conv3d): + module.weight = nn.init.kaiming_normal(module.weight, a=1e-2) + if module.bias is not None: + module.bias = nn.init.constant(module.bias, 0) + + +def softmax_helper(x): + rpt = [1 for _ in range(len(x.size()))] + rpt[1] = x.size(1) + x_max = x.max(1, keepdim=True)[0].repeat(*rpt) + e_x = torch.exp(x - x_max) + return e_x / e_x.sum(1, keepdim=True).repeat(*rpt) + + +class SetNetworkToVal(object): + def __init__(self, use_dropout_sampling=False, norm_use_average=True): + self.norm_use_average = norm_use_average + self.use_dropout_sampling = use_dropout_sampling + + def __call__(self, module): + if isinstance(module, nn.Dropout3d) or isinstance(module, nn.Dropout2d) or isinstance(module, nn.Dropout): + module.train(self.use_dropout_sampling) + elif isinstance(module, nn.InstanceNorm3d) or isinstance(module, nn.InstanceNorm2d) or \ + isinstance(module, nn.InstanceNorm1d) \ + or isinstance(module, nn.BatchNorm2d) or isinstance(module, nn.BatchNorm3d) or \ + isinstance(module, nn.BatchNorm1d): + module.train(not self.norm_use_average) + + +def postprocess_prediction(seg): + # basically look for connected components and choose the largest one, delete everything else + print("running postprocessing... ") + mask = seg != 0 + lbls = label(mask, connectivity=mask.ndim) + lbls_sizes = [np.sum(lbls == i) for i in np.unique(lbls)] + largest_region = np.argmax(lbls_sizes[1:]) + 1 + seg[lbls != largest_region] = 0 + return seg + + +def subdirs(folder, join=True, prefix=None, suffix=None, sort=True): + if join: + l = os.path.join + else: + l = lambda x, y: y + res = [l(folder, i) for i in os.listdir(folder) if os.path.isdir(os.path.join(folder, i)) + and (prefix is None or i.startswith(prefix)) + and (suffix is None or i.endswith(suffix))] + if sort: + res.sort() + return res + + +def subfiles(folder, join=True, prefix=None, suffix=None, sort=True): + if join: + l = os.path.join + else: + l = lambda x, y: y + res = [l(folder, i) for i in os.listdir(folder) if os.path.isfile(os.path.join(folder, i)) + and (prefix is None or i.startswith(prefix)) + and (suffix is None or i.endswith(suffix))] + if sort: + res.sort() + return res + + +subfolders = subdirs # I am tired of confusing those + + +def maybe_mkdir_p(directory): + splits = directory.split("/")[1:] + for i in range(0, len(splits)): + if not os.path.isdir(os.path.join("", *splits[:i+1])): + os.mkdir(os.path.join("", *splits[:i+1])) diff --git a/src/BrainIAC/preprocessing/__init__.py b/src/BrainIAC/preprocessing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t1w.nii b/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t1w.nii new file mode 100644 index 0000000000000000000000000000000000000000..1c3f42145f63f2ef3a7b26e9ac44958491fc8aab --- /dev/null +++ b/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t1w.nii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f10804664000688f0ddc124b39ce3ae27f2c339a40583bd6ff916727e97b77d0 +size 17350930 diff --git a/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t2w.nii b/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t2w.nii new file mode 100644 index 0000000000000000000000000000000000000000..825bf4f1cc52c642511442ba178374884ac8fd44 --- /dev/null +++ b/src/BrainIAC/preprocessing/atlases/nihpd_asym_13.0-18.5_t2w.nii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8496034ae6dbfba1037a5bb51894f28034f73116cc01921a2e7c8b8938b746af +size 17350930 diff --git a/src/BrainIAC/preprocessing/atlases/temp_head.nii.gz b/src/BrainIAC/preprocessing/atlases/temp_head.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..fd8ebd2c8d31b86879b2e0a30732ca0cc1b56ee3 --- /dev/null +++ b/src/BrainIAC/preprocessing/atlases/temp_head.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8eba10e9528a5212b78fb1be477d5e8229751bfec5e1e1a648dc81ff83a622e +size 11976113 diff --git a/src/BrainIAC/preprocessing/dicomtonifti_2.py b/src/BrainIAC/preprocessing/dicomtonifti_2.py new file mode 100644 index 0000000000000000000000000000000000000000..d02c0a3381fd6a950538c436197d8cbf6e73301a --- /dev/null +++ b/src/BrainIAC/preprocessing/dicomtonifti_2.py @@ -0,0 +1,97 @@ +import SimpleITK as sitk +import os +import argparse +import glob +from tqdm import tqdm + +def convert_dicom_series_to_nifti(dicom_dir, output_file): + """ + Convert a single DICOM series to NIFTI format + Args: + dicom_dir: Directory containing DICOM files for one scan + output_file: Output NIFTI file path + Returns: + bool: True if conversion successful, False otherwise + """ + try: + reader = sitk.ImageSeriesReader() + + # get all the scans in the dir + dicom_files = sorted(glob.glob(os.path.join(dicom_dir, "*.dcm"))) + + if not dicom_files: + print(f"No DICOM files found in: {dicom_dir}") + return False + + reader.SetFileNames(dicom_files) + + # load dicom images + image = reader.Execute() + + + sitk.WriteImage(image, output_file) + return True + + except Exception as e: + print(f"Error converting {dicom_dir}: {str(e)}") + return False + +def convert_dicom_to_nifti(input_dir, output_dir): + """ + Convert multiple DICOM series to NIFTI format + Args: + input_dir: Root directory containing subdirectories of DICOM series + output_dir: Output directory for NIFTI files + """ + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + + input_dir = os.path.abspath(input_dir) + output_dir = os.path.abspath(output_dir) + + print(f"Looking for DICOM series in: {input_dir}") + + # Check if directory exists + if not os.path.isdir(input_dir): + print(f"Error: {input_dir} is not a directory") + return + + + scan_dirs = [d for d in os.listdir(input_dir) + if os.path.isdir(os.path.join(input_dir, d))] + + if not scan_dirs: + print("No subdirectories found in the input directory") + return + + print(f"Found {len(scan_dirs)} potential scan directories") + + # Process each scan directory + successful = 0 + failed = 0 + + for scan_dir in tqdm(scan_dirs, desc="Converting scans"): + input_path = os.path.join(input_dir, scan_dir) + output_file = os.path.join(output_dir, f"{scan_dir}.nii.gz") + + if convert_dicom_series_to_nifti(input_path, output_file): + successful += 1 + else: + failed += 1 + + print("\nConversion Summary:") + print(f"Successfully converted: {successful} scans") + print(f"Failed conversions: {failed} scans") + print(f"Output directory: {output_dir}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Convert DICOM series to NIFTI format") + parser.add_argument("--input", "-i", required=True, + help="Input directory containing subdirectories of DICOM series") + parser.add_argument("--output", "-o", required=True, + help="Output directory for NIFTI files") + + args = parser.parse_args() + + convert_dicom_to_nifti(args.input, args.output) \ No newline at end of file diff --git a/src/BrainIAC/preprocessing/hd-bet_params/0.model b/src/BrainIAC/preprocessing/hd-bet_params/0.model new file mode 100644 index 0000000000000000000000000000000000000000..23d2336bed49651cb402e47ec75d81588aa5ce8d --- /dev/null +++ b/src/BrainIAC/preprocessing/hd-bet_params/0.model @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f75233753c4750672815e2b7a86db754995ae44b8f1cd77bccfc37becd2d83c +size 65443735 diff --git a/src/BrainIAC/preprocessing/mri_preprocess_3d_simple.py b/src/BrainIAC/preprocessing/mri_preprocess_3d_simple.py new file mode 100644 index 0000000000000000000000000000000000000000..05b6ce81c51fc76b4cb30c37c849a70bebc458c8 --- /dev/null +++ b/src/BrainIAC/preprocessing/mri_preprocess_3d_simple.py @@ -0,0 +1,215 @@ +import sys +import os +import glob +import SimpleITK as sitk +from tqdm import tqdm +import random +from HD_BET.hd_bet import hd_bet +import argparse +import torch + +def brain_extraction(input_dir, output_dir, device): + """ + Brain extraction using HDBET package (UNet based DL method) + Args: + input_dir {path} -- input directory for registered images + output_dir {path} -- output directory for brain extracted images + Returns: + Brain images + """ + print("Running brain extraction...") + print(f"Input directory: {input_dir}") + print(f"Output directory: {output_dir}") + + # Run HD-BET directly with the output directory + hd_bet(input_dir, output_dir, device=device, mode='fast', tta=0) + + print('Brain extraction complete!') + print("\nContents of output directory after brain extraction:") + print(os.listdir(output_dir)) + +def registration(input_dir, output_dir, temp_img, interp_type='linear'): + """ + MRI registration with SimpleITK + Args: + input_dir {path} -- Directory containing input images + output_dir {path} -- Directory to save registered images + temp_img {str} -- Registration image template + Returns: + The sitk image object -- nii.gz + """ + + # Read the template image + fixed_img = sitk.ReadImage(temp_img, sitk.sitkFloat32) + + # Track problematic files + IDs = [] + print("Preloading step...") + for img_dir in tqdm(sorted(glob.glob(input_dir + '/*.nii.gz'))): + ID = img_dir.split('/')[-1].split('.')[0] + try: + moving_img = sitk.ReadImage(img_dir, sitk.sitkFloat32) + except Exception as e: + IDs.append(ID) + print(f"Error loading {ID}: {e}") + + count = 0 + print("Registering images...") + list_of_files = sorted(glob.glob(input_dir + '/*.nii.gz')) + + for img_dir in tqdm(list_of_files): + ID = img_dir.split('/')[-1].split('.')[0] + if ID in IDs: + print(f'Skipping problematic file: {ID}') + continue + + if "_mask" in ID: + continue + + print(f"Processing image {count + 1}: {ID}") + + try: + # Read and preprocess moving image + moving_img = sitk.ReadImage(img_dir, sitk.sitkFloat32) + moving_img = sitk.N4BiasFieldCorrection(moving_img) + + # Resample fixed image to 1mm isotropic + old_size = fixed_img.GetSize() + old_spacing = fixed_img.GetSpacing() + new_spacing = (1, 1, 1) + new_size = [ + int(round((old_size[0] * old_spacing[0]) / float(new_spacing[0]))), + int(round((old_size[1] * old_spacing[1]) / float(new_spacing[1]))), + int(round((old_size[2] * old_spacing[2]) / float(new_spacing[2]))) + ] + + # Set interpolation type + if interp_type == 'linear': + interp_type = sitk.sitkLinear + elif interp_type == 'bspline': + interp_type = sitk.sitkBSpline + elif interp_type == 'nearest_neighbor': + interp_type = sitk.sitkNearestNeighbor + + # Resample fixed image + resample = sitk.ResampleImageFilter() + resample.SetOutputSpacing(new_spacing) + resample.SetSize(new_size) + resample.SetOutputOrigin(fixed_img.GetOrigin()) + resample.SetOutputDirection(fixed_img.GetDirection()) + resample.SetInterpolator(interp_type) + resample.SetDefaultPixelValue(fixed_img.GetPixelIDValue()) + resample.SetOutputPixelType(sitk.sitkFloat32) + fixed_img = resample.Execute(fixed_img) + + # Initialize transform + transform = sitk.CenteredTransformInitializer( + fixed_img, + moving_img, + sitk.Euler3DTransform(), + sitk.CenteredTransformInitializerFilter.GEOMETRY) + + # Set up registration method + registration_method = sitk.ImageRegistrationMethod() + registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50) + registration_method.SetMetricSamplingStrategy(registration_method.RANDOM) + registration_method.SetMetricSamplingPercentage(0.01) + registration_method.SetInterpolator(sitk.sitkLinear) + registration_method.SetOptimizerAsGradientDescent( + learningRate=1.0, + numberOfIterations=100, + convergenceMinimumValue=1e-6, + convergenceWindowSize=10) + registration_method.SetOptimizerScalesFromPhysicalShift() + registration_method.SetShrinkFactorsPerLevel(shrinkFactors=[4, 2, 1]) + registration_method.SetSmoothingSigmasPerLevel(smoothingSigmas=[2, 1, 0]) + registration_method.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn() + registration_method.SetInitialTransform(transform) + + # Execute registration + final_transform = registration_method.Execute(fixed_img, moving_img) + + # Apply transform and save registered image + moving_img_resampled = sitk.Resample( + moving_img, + fixed_img, + final_transform, + sitk.sitkLinear, + 0.0, + moving_img.GetPixelID()) + + # Save with _0000 suffix as required by HD-BET + output_filename = os.path.join(output_dir, f"{ID}_0000.nii.gz") + sitk.WriteImage(moving_img_resampled, output_filename) + print(f"Saved registered image to: {output_filename}") + count += 1 + + except Exception as e: + print(f"Error processing {ID}: {e}") + continue + + print(f"Successfully registered {count} images.") + # Debug information + print(f"Contents of output directory {output_dir}:") + print(os.listdir(output_dir)) + return count > 0 + +def main(temp_img, input_dir, output_dir): + """ + Main function to process brain MRI images + Args: + temp_img {str} -- Path to template image + input_dir {str} -- Path to input directory containing images + output_dir {str} -- Path to output directory for results + """ + + os.makedirs(output_dir, exist_ok=True) + + # set device + device = "0" if torch.cuda.is_available() else "cpu" + + # Create temporary directory for intermediate results + temp_reg_dir = os.path.join(output_dir, 'temp_registered') + os.makedirs(temp_reg_dir, exist_ok=True) + + print("Starting brain MRI preprocessing...") + + # REgistration + print("\nStep 1: Image Registration") + success = registration( + input_dir=input_dir, + output_dir=temp_reg_dir, + temp_img=temp_img + ) + + if not success: + print("Registration failed! No images were processed successfully.") + return + + print("\nChecking temporary directory contents:") + print(os.listdir(temp_reg_dir)) + + # skullstripping + print("\nStep 2: Brain Extraction") + brain_extraction( + input_dir=temp_reg_dir, + output_dir=output_dir, + device=device + ) + + # Clean up temporary directory + import shutil + shutil.rmtree(temp_reg_dir) + + print("\nPreprocessing complete! Final results saved in:", output_dir) + print("Final preprocessed files:") + print(os.listdir(output_dir)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Process brain MRI registration and skull stripping.") + parser.add_argument("--temp_img", type=str, required=True, help="Path to the atlas template image.") + parser.add_argument("--input_dir", type=str, required=True, help="Path to the input images directory.") + parser.add_argument("--output_dir", type=str, required=True, help="Path to save the processed images.") + + args = parser.parse_args() + main(temp_img=args.temp_img, input_dir=args.input_dir, output_dir=args.output_dir) \ No newline at end of file diff --git a/src/BrainIAC/preprocessing/preprocess_utils.py b/src/BrainIAC/preprocessing/preprocess_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4988111f5fc96f3e4856ec592e20f66d30de756e --- /dev/null +++ b/src/BrainIAC/preprocessing/preprocess_utils.py @@ -0,0 +1,511 @@ +import sys +sys.path.append('../TM2_segmentation') + +import os +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + +import logging +import SimpleITK as sitk +from scipy.signal import medfilt +import numpy as np +import nibabel as nib +import scipy +import skimage +import functools +from skimage.transform import resize +import subprocess +import pandas as pd +import shutil +import itk + +# compute the intersection over union of two binary masks +def iou(component1, component2): + component1 = np.array(component1, dtype=bool) + component2 = np.array(component2, dtype=bool) + + overlap = component1 * component2 # Logical AND + union = component1 + component2 # Logical OR + + IOU = overlap.sum()/float(union.sum()) + return IOU + +# helper function to get the id and path of the image and the mask +def get_id_and_path(row, image_dir, nested = False, no_tms=True): + patient_id, image_path, ltm_file, rtm_file = "","","","" + if no_tms and row['Ok registered? Y/N'] == "N" : + print("skip - bad registration") + return "","","","" + if "NDAR" in str(row['Filename']) and nested==False and no_tms: + patient_id = str(row['Filename']).split("_")[0] + else: + patient_id = str(row['Filename']).split(".")[0] + + path = find_file_in_path(patient_id, os.listdir(image_dir)) + + if nested: + patient_id = patient_id.split("/")[-1] + path = patient_id.split("/")[-1] + if no_tms==False: + path="" + + scan_folder = image_dir+path + patient_id=patient_id.split("/")[-1] + + for file in os.listdir(scan_folder): + t = image_dir+path+"/"+file + if "LTM" in file: + ltm_file = t + elif "RTM" in file: + rtm_file = t + elif "TM" in file: + rtm_file = t + ltm_file = t + if patient_id in file: + image_path = t + return patient_id, image_path, ltm_file, rtm_file + +# another helper function to get the id and path of the image and the mask, when the folder structure is different +def get_id_and_path_not_nested(row, image_dir, masks_dir): + patient_id, image_path, tm_file = 0,0,0 + if row['Ok registered? Y/N'] == "N": + print("skip - bad registration") + return 0,0,0,0 + if "NDAR" in row['Filename']: + patient_id = row['Filename'].split("_")[0] + else: + patient_id = row['Filename'].split(".")[0] + + path = find_file_in_path(patient_id, os.listdir(masks_dir)) + if len(path)<3: + return 0,0,0,0 + scan_folder_masks = masks_dir+path + + for file in os.listdir(scan_folder_masks): + if "._" in file: #skip hidden files + continue + if "TM" in file: + tm_file = masks_dir+path+"/"+file + elif ".nii" in file and "TM" not in file: + image_path = image_dir+patient_id+".nii" + + return patient_id, image_path, tm_file + +# crop the image to the bounding box of the mask +def crop_center(img,cropx,cropy): + y,x = img.shape + startx = x//2-(cropx//2) + starty = y//2-(cropy//2) + return img[starty:starty+cropy,startx:startx+cropx] + +# find the file in the path +def find_file_in_path(name, path): + result = [] + result = list(filter(lambda x:name in x, path)) + if len(result) != 0: + for file in result: + if "._" in file:#skip hidden files + continue + else: + return file + else: + return "" + +# perform the bias field correction +def bias_field_correction(img): + image = sitk.GetImageFromArray(img) + maskImage = sitk.OtsuThreshold(image, 0, 1, 200) + corrector = sitk.N4BiasFieldCorrectionImageFilter() + numberFittingLevels = 4 + + corrector.SetMaximumNumberOfIterations([100] * numberFittingLevels) + corrected_image = corrector.Execute(image, maskImage) + log_bias_field = corrector.GetLogBiasFieldAsImage(image) + corrected_image_full_resolution = image / sitk.Exp(log_bias_field) + return sitk.GetArrayFromImage(corrected_image_full_resolution) + +def load_nii(path): + nii = nib.load(path) + return nii.get_fdata(), nii.affine + +def save_nii(data, path, affine): + nib.save(nib.Nifti1Image(data, affine), path) + return + +def denoise(volume, kernel_size=3): + return medfilt(volume, kernel_size) + +# apply the windowing to the image +def apply_window(image, win_centre= 40, win_width= 400): + range_bottom = 149 #win_centre - win_width / 2 + scale = 256 / 256 #win_width + image = image - range_bottom + + image = image * scale + image[image < 0] = 0 + image[image > 255] = 255 + return image + +# rescale the intensity of the image and binning +def rescale_intensity(volume, percentils=[0.5, 99.5], bins_num=256): + #remove background pixels by the otsu filtering + t = skimage.filters.threshold_otsu(volume,nbins=6) + volume[volume < t] = 0 + + obj_volume = volume[np.where(volume > 0)] + min_value = np.percentile(obj_volume, percentils[0]) + max_value = np.percentile(obj_volume, percentils[1]) + if bins_num == 0: + obj_volume = (obj_volume - min_value) / (max_value - min_value).astype(np.float32) + else: + obj_volume = np.round((obj_volume - min_value) / (max_value - min_value) * (bins_num - 1)) + obj_volume[np.where(obj_volume < 1)] = 1 + obj_volume[np.where(obj_volume > (bins_num - 1))] = bins_num - 1 + + volume = volume.astype(obj_volume.dtype) + volume[np.where(volume > 0)] = obj_volume + return volume + +# equalize the histogram of the image +def equalize_hist(volume, bins_num=256): + obj_volume = volume[np.where(volume > 0)] + hist, bins = np.histogram(obj_volume, bins_num) + cdf = hist.cumsum() + cdf = (bins_num - 1) * cdf / cdf[-1] + + obj_volume = np.round(np.interp(obj_volume, bins[:-1], cdf)).astype(obj_volume.dtype) + volume[np.where(volume > 0)] = obj_volume + return volume + +# enhance the image +def enhance(volume, kernel_size=3, + percentils=[0.5, 99.5], bins_num=256, eh=True): + try: + volume = bias_field_correction(volume) + volume = denoise(volume, kernel_size) + volume = rescale_intensity(volume, percentils, bins_num) + if eh: + volume = equalize_hist(volume, bins_num) + return volume + except RuntimeError: + logging.warning('Failed enchancing') + +# enhance the image without bias field correction +def enhance_noN4(volume, kernel_size=3, + percentils=[0.5, 99.5], bins_num=256, eh=True): + try: + #volume = bias_field_correction(volume) + volume = denoise(volume, kernel_size) + #print(np.shape(volume)) + volume = rescale_intensity(volume, percentils, bins_num) + #print(np.shape(volume)) + if eh: + volume = equalize_hist(volume, bins_num) + return volume + except RuntimeError: + logging.warning('Failed enchancing') + +# get the resampled image +def get_resampled_sitk(data_sitk,target_spacing): + new_spacing = target_spacing + + orig_spacing = data_sitk.GetSpacing() + orig_size = data_sitk.GetSize() + + new_size = [int(orig_size[0] * orig_spacing[0] / new_spacing[0]), + int(orig_size[1] * orig_spacing[1] / new_spacing[1]), + int(orig_size[2] * orig_spacing[2] / new_spacing[2])] + + res_filter = sitk.ResampleImageFilter() + img_sitk = res_filter.Execute(data_sitk, + new_size, + sitk.Transform(), + sitk.sitkLinear, + data_sitk.GetOrigin(), + new_spacing, + data_sitk.GetDirection(), + 0, + data_sitk.GetPixelIDValue()) + + return img_sitk + +# convert the nrrd file to nifty file +def nrrd_to_nifty(nrrd_file): + _nrrd = nrrd.read(nrrd_file) + data_f = _nrrd[0] + header = _nrrd[1] + return np.asarray(data_f), header + +# crop the brain from the image +def crop_brain(var_img, mni_img): + # invert brain mask + inverted_mask = np.invert(mni_img.astype(bool)).astype(float) + mask_data = inverted_mask * var_img + return mask_data + +# normalize the image with the brain mask +def brain_norm_masked(mask_data, brain_data, to_save=False): + masked = crop_brain(brain_data, mask_data) + enhanced = enhance(masked) + return enhanced + +# enhance all the images in the path +def enhance_and_debias_all_in_path(image_dir='data/mni_templates_BK/',path_to='data/denoised_mris/',\ + input_annotation_file = 'data/all_metadata.csv'): + + df = pd.read_csv(input_annotation_file,header=0) + df=df[df['Ok registered? Y/N']=='Y'].reset_index() + #print(df.shape[0]) + for idx in range(0, 1): + print(idx) + row = df.iloc[idx] + patient_id, image_path, tm_file, _ = get_id_and_path(row, image_dir) + print(patient_id, image_path, tm_file) + image_sitk = sitk.ReadImage(image_path) + image_array = sitk.GetArrayFromImage(image_sitk) + image_array = enhance(image_array) + image3 = sitk.GetImageFromArray(image_array) + sitk.WriteImage(image3,path_to+patient_id+'.nii') + return + +# Z-enhance all the images in the path +def z_enhance_and_debias_all_in_path(image_dir='data/mni_templates_BK/',path_to='data/z_scored_mris/',\ + input_annotation_file = 'data/all_metadata.csv', for_training=True, annotations=True): + df = pd.read_csv(input_annotation_file,header=0) + + if for_training: + df=df[df['Ok registered? Y/N']=='Y'].reset_index() + print(df.shape[0]) + + for idx in range(0, df.shape[0]): + print(idx) + row = df.iloc[idx] + patient_id, image_path, tm_file, _ = get_id_and_path(row, image_dir, nested=False, no_tms=for_training) + print(patient_id, len(image_path), tm_file, path_to) + if not os.path.isdir(path_to+"no_z"): + os.mkdir(path_to+"no_z") + if not os.path.isdir(path_to+"z"): + os.mkdir(path_to+"z") + + if len(image_path)>3: + image_sitk = sitk.ReadImage(image_path) + image_array = sitk.GetArrayFromImage(image_sitk) + print(len(image_array)) + try: + image_array = enhance_noN4(image_array) + image3 = sitk.GetImageFromArray(image_array) + sitk.WriteImage(image3,path_to+"no_z/"+patient_id+'.nii') + os.mkdir(path_to+"z/"+patient_id) + if annotations: + shutil.copyfile(tm_file, path_to+"z/"+patient_id+"/TM.nii.gz") + duck_line = "zscore-normalize "+path_to+"no_z/"+patient_id+".nii -o "+path_to+"z/"+patient_id +"/"+patient_id+'.nii' + subprocess.getoutput(duck_line) + except: + continue + +# find the closest value in the list +def closest_value(input_list, input_value): + arr = np.asarray(input_list) + i = (np.abs(arr - input_value)).argmin() + return arr[i], i + +# find the centile of the input value +def find_centile(input_tmt, age, df): + #print("TMT:",input_tmt,"Age:", age) + val,i=closest_value(df['x'],age) + + centile = 'out of range' + if input_tmtdf.iloc[i]['X97']: + centile ='97>' + #print(val,i,centile) + return centile + +# find the exact percentile of the input value +def find_exact_percentile_return_number(input_tmt, age, df): + #print("TMT:",input_tmt,"Age:", age) + val,i=closest_value(df['x'],age) + + mu = df.iloc[i]['mu'] + sigma = df.iloc[i]['sigma'] + nu = df.iloc[i]['nu'] + #tau = df.iloc[i]['tau'] + + if nu!=0: + z = ((input_tmt/mu)**(nu)-1)/(nu*sigma) + else: + z = 1/sigma * math.log(input_tmt/mu) + percentile = scipy.stats.norm.cdf(z) + return round(percentile*100,2) + +# add median labels to boxplots +def add_median_labels(ax, fmt='.1f'): + lines = ax.get_lines() + boxes = [c for c in ax.get_children() if type(c).__name__ == 'PathPatch'] + lines_per_box = int(len(lines) / len(boxes)) + for median in lines[4:len(lines):lines_per_box]: + x, y = (data.mean() for data in median.get_data()) + # choose value depending on horizontal or vertical plot orientation + value = x if (median.get_xdata()[1] - median.get_xdata()[0]) == 0 else y + text = ax.text(x, y, f'{value:{fmt}}', ha='center', va='center', + fontweight='ultralight', color='gray') + # create median-colored border around white text for contrast + text.set_path_effects([ + path_effects.Stroke(linewidth=3, foreground=median.get_color()), + path_effects.Normal(), + ]) + +#register to a template +def register_to_template(input_image_path, output_path, fixed_image_path,create_subfolder=True): + fixed_image = itk.imread(fixed_image_path, itk.F) + + # Import Parameter Map + parameter_object = itk.ParameterObject.New() + parameter_object.AddParameterFile('data/golden_image/mni_templates/Parameters_Rigid.txt') + + if "nii" in input_image_path and "._" not in input_image_path: + print(input_image_path) + + # Call registration function + try: + moving_image = itk.imread(input_image_path, itk.F) + result_image, result_transform_parameters = itk.elastix_registration_method( + fixed_image, moving_image, + parameter_object=parameter_object, + log_to_console=False) + image_id = input_image_path.split("/")[-1] + + if create_subfolder: + new_dir = output_path+image_id.split(".")[0] + if not os.path.exists(new_dir): + os.mkdir(new_dir) + itk.imwrite(result_image, new_dir+"/"+image_id) + else: + itk.imwrite(result_image, output_path+"/"+image_id) + + print("Registered ", image_id) + except: + print("Cannot transform", input_image_path.split("/")[-1]) + +if __name__=="__main__": + # replace header with ,AGE_M,SEX,SCAN_PATH,Filename,dataset + ''' + z_enhance_and_debias_all_in_path(image_dir='data/mni_templates_BK/', + path_to='data/z_scored_mris/z_with_pseudo/',\ + input_annotation_file = 'data/all_metadata.csv') + + z_enhance_and_debias_all_in_path(image_dir='data/curated_test/reg_tm_not_corrected/', + path_to='data/curated_test/final_test/', + input_annotation_file = 'data/curated_test/reg_tm_not_corrected/Dataset_test_rescaled.csv', + for_training=True) + # all the datasets + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/registered_not_ench/', + path_to='data/t1_mris/registered/', + input_annotation_file = 'data/Dataset_t1_healthy_raw.csv', + for_training=False,annotations=False) + + #ping + # z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/pings_registered/', + path_to='data/t1_mris/pings_ench_reg/', + input_annotation_file = 'data/Dataset_ping.csv', + for_training=False, annotations=False) + # pixar + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/pixar/', + path_to='data/t1_mris/pixar_ench/', + input_annotation_file = 'data/Dataset_pixar.csv', + for_training=False, annotations=False) + #abide + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/abide_registered/', + path_to='data/t1_mris/abide_ench_reg/', + input_annotation_file = "data/Dataset_abide.csv", + for_training=False, annotations=False) + + # calgary + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/calgary_reg/', + path_to='data/t1_mris/calgary_reg_ench/', + input_annotation_file = "data/Dataset_calgary.csv", + for_training=False, annotations=False) + + # aomic replace header with ,AGE_M,SEX,SCAN_PATH,Filename,dataset + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/aomic_reg/', + path_to='data/t1_mris/aomic_reg_ench/', + input_annotation_file = "data/Dataset_aomic.csv", + for_training=False, annotations=False) + + # NIHM replace header with ,AGE_M,SEX,SCAN_PATH,Filename,dataset + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/nihm_reg/', + path_to='data/t1_mris/nihm_ench_reg/', + input_annotation_file = "data/Dataset_nihm.csv", + for_training=False, annotations=False) + + # ICBM replace header with ,AGE_M,SEX,SCAN_PATH,Filename,dataset + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/icbm_reg/', + path_to='data/t1_mris/icbm_ench_reg/', + input_annotation_file = "data/Dataset_icbm.csv", + for_training=False, annotations=False) + + # SALD + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/sald_reg/', + path_to='data/t1_mris/sald_reg_ench/', + input_annotation_file = "data/Dataset_sald.csv", + for_training=False, annotations=False) + + ## NYU + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/nyu_reg/', + path_to='data/t1_mris/nyu_reg_ench/', + input_annotation_file = "data/Dataset_nyu.csv", + for_training=False, annotations=False) + ## NAH + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/healthy_adults_nihm/', + path_to='data/t1_mris/healthy_adults_nihm_reg_ench/', + input_annotation_file = "data/Dataset_healthy_adults_nihm.csv", + for_training=False, annotations=False) + ## Petfrog + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/petfrog_reg/', + path_to='data/t1_mris/petfrog_reg_ench/', + input_annotation_file = "data/Dataset_petfrog.csv", + for_training=False, annotations=False) + ## CBTN + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/cbtn_reg/', + path_to='data/t1_mris/cbtn_reg_ench/', + input_annotation_file = "data/Dataset_cbtn.csv", + for_training=False, annotations=False) + ## DMG + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/dmg_reg/', + path_to='data/t1_mris/dmg_reg_ench/', + input_annotation_file = "data/Dataset_dmg.csv", + for_training=False, annotations=False) + ## BCH + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/bch_reg/', + path_to='data/t1_mris/bch_reg_ench/', + input_annotation_file = "data/Dataset_bch.csv", + for_training=False, annotations=False) + ## BCH long + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/bch_long_reg/', + path_to='data/t1_mris/bch_long_reg_ench/', + input_annotation_file = "data/Dataset_bch_long.csv", + for_training=False, annotations=False) + ## 28 + z_enhance_and_debias_all_in_path(image_dir='data/t1_mris/uscf_reg/', + path_to='data/t1_mris/uscf_reg_ench/', + input_annotation_file = "data/Dataset_ucsf.csv", + for_training=False, annotations=False)''' + ## BCH long masked test + z_enhance_and_debias_all_in_path(image_dir='data/bch_long_pre_test/reg/', + path_to='data/bch_long_pre_test/reg_ench/', + input_annotation_file = "data/Dataset_bch_long_pre_test.csv", + for_training=False, annotations=False) + + \ No newline at end of file diff --git a/src/BrainIAC/quickstart.ipynb b/src/BrainIAC/quickstart.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d14c8ba9198116fba1c06d416cac7d5a281befb7 --- /dev/null +++ b/src/BrainIAC/quickstart.ipynb @@ -0,0 +1,699 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preprocessing " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### if the MR is in dicom " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Looking for DICOM series in: /media/data/BrainIAC/src/BrainIAC/data/dicom\n", + "Found 2 potential scan directories\n", + "Converting scans: 100%|███████████████████████████| 2/2 [00:00<00:00, 15.50it/s]\n", + "\n", + "Conversion Summary:\n", + "Successfully converted: 2 scans\n", + "Failed conversions: 0 scans\n", + "Output directory: /media/data/BrainIAC/src/BrainIAC/data\n" + ] + } + ], + "source": [ + "!python ./preprocessing/dicomtonifti_2.py -i ./data/dicom -o ./data/unprocessed\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### if the MRs are in nifti " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting brain MRI preprocessing...\n", + "\n", + "Step 1: Image Registration\n", + "Preloading step...\n", + "100%|█████████████████████████████████████████████| 4/4 [00:00<00:00, 31.93it/s]\n", + "Registering images...\n", + " 0%| | 0/4 [00:00 12 seconds on CPU\n", + "-> 1.5 seconds on GPU (Nvidia A600)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BrainIAC Loaded!!\n", + "Generating saliency maps: 0%| | 0/1 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import nibabel as nib\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import cv2\n", + "\n", + "# Load the NIfTI files\n", + "mri_nifti_path = f'./data/output/00001_image.nii.gz' \n", + "saliency_nifti_path = f'./data/output/00001_saliencymap.nii.gz'\n", + "\n", + "mri_img = nib.load(mri_nifti_path)\n", + "saliency_img = nib.load(saliency_nifti_path)\n", + "mri_data = mri_img.get_fdata()\n", + "saliency_data = saliency_img.get_fdata()\n", + "\n", + "print(mri_data.shape, saliency_data.shape, np.max(mri_data), np.max(saliency_data))\n", + "\n", + "# Select a 2D slice based on a index number \n", + "slice_idx = mri_data.shape[2] // 2 \n", + "mri_slice = mri_data[:, :, slice_idx]\n", + "saliency_slice = saliency_data[:, :, slice_idx]\n", + "\n", + "# Normalize the MRI slice using robust normalization (1st and 99th percentiles)\n", + "def normalize_image(img):\n", + " p1, p99 = np.percentile(img, (1, 99))\n", + " img_clipped = np.clip(img, p1, p99)\n", + " img_normalized = (img_clipped - p1) / (p99 - p1)\n", + " return img_normalized\n", + "\n", + "mri_slice_norm = normalize_image(mri_slice)\n", + "\n", + "# Normalization function \n", + "def normalize_saliency(saliency):\n", + " saliency[saliency < 0] = 0\n", + " if np.max(saliency) > 0:\n", + " saliency_normalized = saliency / np.max(saliency)\n", + " else:\n", + " saliency_normalized = saliency\n", + " return saliency_normalized\n", + "\n", + "# Apply Gaussian blur to smooth the saliency map and normalize\n", + "saliency_slice_blurred = cv2.GaussianBlur(saliency_slice, (15, 15), 0)\n", + "saliency_slice_norm = normalize_saliency(saliency_slice_blurred)\n", + "\n", + "# Apply a threshold to the saliency map\n", + "threshold_value = 0.0 \n", + "saliency_slice_thresholded = np.where(saliency_slice_norm > threshold_value, saliency_slice_norm, 0)\n", + "\n", + "# thresholded saliency map\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(saliency_slice_thresholded.T, cmap='magma', interpolation='none', origin='lower')\n", + "plt.axis('off') \n", + "\n", + "# MRI slice with contour overlay of the thresholded saliency map\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(mri_slice_norm.T, cmap='gray', interpolation='none', origin='lower')\n", + "plt.contour(saliency_slice_thresholded.T, levels=10, cmap='magma', origin='lower', linewidths=3) # Adjust the linewidth value as needed\n", + "plt.axis('off') # Remove axis for better visualization\n", + "\n", + "\n", + "# MRI slice alone (no overlay)\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(mri_slice_norm.T, cmap='gray', interpolation='none', origin='lower')\n", + "plt.axis('off') \n", + "\n", + "\n", + "plt.show() \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## BrainIAC - Feature extraction " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BrainIAC Loaded!!\n", + "Inference: 100%|███████████████████████████████| 1/1 [00:00<00:00, 2.58batch/s]\n", + "Features saved to ./data/output/features.csv\n" + ] + } + ], + "source": [ + "## extract feature vector from the BrainIAC model \n", + "\n", + "!python get_brainiac_features.py \\\n", + " --checkpoint ./checkpoints/BrainIAC.ckpt \\\n", + " --input_csv ./data/csvs/input_scans.csv \\\n", + " --output_csv ./data/output/features.csv \\\n", + " --root_dir ./data/sample/processed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Brain age prediction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "runtime :\n", + "-> 1 second on GPU (Nvidia A6000) for a single scan inference \n", + "-> 2 seconds on CPU for a single scan inference " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model and checkpoint loaded!\n", + "Inference: 0%| | 0/1 [00:00 + + + + + Brain Age Prediction + + + +
+ + Kann Lab Logo + + + + Brain Age Logo +
+ +
+

Brain Age Prediction

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
    + {% for category, message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + +
+
+ + + + + + + + + +
+ +
+ + +
+ +
+ + +
+ + + + + +
+ + {% if prediction %} +
+

Prediction Result

+

Predicted Brain Age: {{ prediction }}

+
+ {% endif %} + + {# Saliency Map Display Section - Modified for Dynamic Loading #} + {% if saliency_info %} +
+

Saliency Map Visualizations (Axial View)

+ + {# Slice Slider and Label #} +
+
+ {# Store unique_id AND temp_dir_path as data attributes #} + + {# Small loading text #} +
+ +
+
+

Input Slice

+ {# Use initial center slice data #} + Input MRI Slice +
+
+

Saliency Heatmap

+ {# Use initial center slice data #} + Saliency Heatmap +
+
+

Overlay

+ {# Use initial center slice data #} + Saliency Overlay +
+
+
+ + {# JavaScript for Slider Interaction with Fetch #} + + + {% endif %} +
+ + +
+
+

Processing... Please wait.

+
+ + + + \ No newline at end of file diff --git a/src/BrainIAC/util.ipynb b/src/BrainIAC/util.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c2af15d3cafc4e33e3503dac0f693519c1e3cf5b --- /dev/null +++ b/src/BrainIAC/util.ipynb @@ -0,0 +1,196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load BrainIAC" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch \n", + "from load_brainiac import load_brainiac\n", + "\n", + "# path to the checkpoint\n", + "checkpoint = \"/path/to/brainiac_weights\"\n", + "device = \"cuda\"\n", + "\n", + "model = load_brainiac(checkpoint, device)\n", + "print(f\"Model loaded successfully from {checkpoint}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vizualize Saliency Maps " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(128, 128, 128) (128, 128, 128) 1.0 0.0016589891165494919\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeEAAAHiCAYAAADf3nSgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA8PUlEQVR4nO3d2e9dVf3/8YXDF2elUCht6Uxb2tIyTzGRCk6JxgvjlOiVidf+I957Y2Ki3HFhiCIxMREJBpShCJSWtralIxTqgPPE78L8tq/1bM86LW15t+X5uNo7+3z2uPZn5bzf573WJW+88cYbTZIkveXeUX0CkiS9XdkJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkoq861Q/eMkll5zL85Ak6aJyKgNS+k1YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5YkqYidsCRJReyEJUkqYicsSVIRO2FJkorYCUuSVMROWJKkInbCkiQVsROWJKmInbAkSUXshCVJKmInLElSETthSZKK2AlLklTETliSpCJ2wpIkFbETliSpiJ2wJElF7IQlSSpiJyxJUhE7YUmSitgJS5JUxE5YkqQidsKSJBWxE5Ykqci7qk9A9T7wgQ906ytWrJiWr7jiim7b+vXru/V///vf0/K//vWvbtvTTz89LW/btu0Mz1KSLj5+E5YkqYidsCRJReyEJUkqYk74InXJJZd06x/60Iem5auuuqrb9qlPfapb//jHPz4tX3bZZd225cuXd+vvetf/mtCf//znbtujjz46LX/3u9/ttv3hD3/o1vNv9+3b16S3wsKFC7v1yy+/fOZns61znb+HGK3/8Y9/7Lb94x//mJb5Dv3zn/+ceT66OPhNWJKkInbCkiQVMRx9kVq0aFG3vmHDhpMut9baV7/61W49S5QYDnvHO94xc53huixn2rx5c7ft4MGD3fo111wzLT/88MPdtu3btzfpbPnwhz88LW/durXbtmnTpm49y/fe9773ddsy5cP0zzvf+c5uPdMvv/71r7ttR48enZYPHz7cbXvttde69VdffbXp4uI3YUmSitgJS5JUxE5YkqQi5oTPc+95z3umZZZPZB61tdauvvrqaXnjxo3dtptuumla5lCUH/nIR7r1LJlgHuz//u//uvUsveC2zIO9973vHZ57XueqVau6bQsWLJiW//rXv3bb/v73v3frL7/88rR87NixpreH97///d36ddddNy3z9xHZ9m6//fZu25VXXtmtZ5tmW8vfQ2SeubXWPvjBD3br7373u6fl1atXd9t+85vfTMss3Vu5cmW3vnfv3mn5kUce6bbt2LGjW+e+dH7ym7AkSUXshCVJKmI4+jyQoSqGaTOs9rGPfazbxtGrMoycZUat9eEylk9wFqUMQecsSa219vrrr3frGWZm6Povf/nLtLxkyZJu2xtvvNGt79q1a+YxM+y3dOnSbhvXM3zImZuee+65adlQ9YWNo77ddddd3fott9wyLbONZIh38eLF3bZ8F1vr0x8M72Z7Z0on0yut9SVMTNvke8wRs5g6+v3vfz8tr127ttv2k5/8pFv/8Y9/PC3n/wadX/wmLElSETthSZKK2AlLklTEnHABDu947bXXTsuf//znu20333zztHzjjTd225jLzZwVc1vHjx+flllqwVxu5mQ548vf/va3bj1LMf7zn/9027J86dJLL+22MbecsiSptf5aOGxmliTxfHI2qNb6+/zTn/602+bMTRcWlu7ccMMNM7fzdxb8vURi7jTfBb63+f6xJInvVL5zfBdyne8tfzuR58ffhFx//fXd+uOPPz4tczhMnT/8JixJUhE7YUmSitgJS5JUxJzwW4D5Ig5b94UvfGFa/uhHP9ptG9XsZh1ua32O6k9/+lO3LackZE6K0xVmrWLmdU8ma46Zv8ocGnNkzC3nkJwcAjDzYMzZMWed585a4MyZMX/24IMPduusuVS9/K3Ahz70oW4b87w5JeHChQu7bdnW5k0NyN85pHwfOZwq36ls76zTz3eMv9dgPTS3J9Yq5xgDHDb2wIED07I1xLX8JixJUhE7YUmSihiOfgvk7EattbZ169ZuPUPQDD9lGGs0ZGRrffiXIefcNiolaq0PK+cweSfbbw7Pl0PztdaHCFl6wZKJnL1mVJbBkiSG5POzDEdnSQfLvRie/tSnPjUtHzx4sNt23333detHjhxpOvsuu+yybn3Lli3TMmcJy/Kz1vrhKFnKl2VubLNcz9QIQ8zZ9hnS5RCX2U5Z6pQpFO6Hx8y/ZbkeZ1nLsi2W/eV7vH///pnno3PPb8KSJBWxE5YkqYidsCRJRcwJnyNZQnHHHXd021iGlLkv5mMyR8RcLvNXmftiSVBOicacK/NOWXrBz7JMijnjlKVZa9asmbmttb6kivcgt73//e+fua211l555ZVpmdPJZe6b+fXMSbfW2je+8Y1pmTlFDnF5//33N51969at69Yzp88pCDk9YOZL+d5k+2LJD/OsmYMdlRaxVI6fzXeVbS/fodG5ttaXIfH9Z148f1+S70Vrra1fv35a5u8snn/++W79qaeempZZiqUz5zdhSZKK2AlLklTEcPQ5kuU5DD9z9pPEcE+GXxlyYygtZ0riqFMZnmYYaxSOJobSXnvttZnnl+FgljYtW7asW89SLIa48/xYdsT1DBFyBKEMyfN8WDL1q1/9alrmDD3f/OY3u/UsFXvooYeG56cey+zWrl07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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import nibabel as nib\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import cv2\n", + "\n", + "# Load the NIfTI files\n", + "mri_nifti_path = f'/media/data/BrainIAC/src/BrainIAC/data/output/bchpostop2911_image.nii.gz' \n", + "saliency_nifti_path = f'/media/data/BrainIAC/src/BrainIAC/data/output/bchpostop2911_saliencymap.nii.gz'\n", + "\n", + "mri_img = nib.load(mri_nifti_path)\n", + "saliency_img = nib.load(saliency_nifti_path)\n", + "mri_data = mri_img.get_fdata()\n", + "saliency_data = saliency_img.get_fdata()\n", + "\n", + "print(mri_data.shape, saliency_data.shape, np.max(mri_data), np.max(saliency_data))\n", + "\n", + "# Select a 2D slice based on a index number \n", + "slice_idx = mri_data.shape[2] // 2 - 35\n", + "mri_slice = mri_data[:, :, slice_idx]\n", + "saliency_slice = saliency_data[:, :, slice_idx]\n", + "\n", + "# Normalize the MRI slice using robust normalization (1st and 99th percentiles)\n", + "def normalize_image(img):\n", + " p1, p99 = np.percentile(img, (1, 99))\n", + " img_clipped = np.clip(img, p1, p99)\n", + " img_normalized = (img_clipped - p1) / (p99 - p1)\n", + " return img_normalized\n", + "\n", + "mri_slice_norm = normalize_image(mri_slice)\n", + "\n", + "# Normalization function \n", + "def normalize_saliency(saliency):\n", + " saliency[saliency < 0] = 0\n", + " if np.max(saliency) > 0:\n", + " saliency_normalized = saliency / np.max(saliency)\n", + " else:\n", + " saliency_normalized = saliency\n", + " return saliency_normalized\n", + "\n", + "# Apply Gaussian blur to smooth the saliency map and normalize\n", + "saliency_slice_blurred = cv2.GaussianBlur(saliency_slice, (15, 15), 0)\n", + "saliency_slice_norm = normalize_saliency(saliency_slice_blurred)\n", + "\n", + "# Apply a threshold to the saliency map\n", + "threshold_value = 0.0 \n", + "saliency_slice_thresholded = np.where(saliency_slice_norm > threshold_value, saliency_slice_norm, 0)\n", + "\n", + "# thresholded saliency map\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(saliency_slice_thresholded.T, cmap='magma', interpolation='none', origin='lower')\n", + "plt.axis('off') \n", + "\n", + "# MRI slice with contour overlay of the thresholded saliency map\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(mri_slice_norm.T, cmap='gray', interpolation='none', origin='lower')\n", + "plt.contour(saliency_slice_thresholded.T, levels=10, cmap='magma', origin='lower', linewidths=3) # Adjust the linewidth value as needed\n", + "plt.axis('off') # Remove axis for better visualization\n", + "\n", + "\n", + "# MRI slice alone (no overlay)\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(mri_slice_norm.T, cmap='gray', interpolation='none', origin='lower')\n", + "plt.axis('off') \n", + "\n", + "\n", + "plt.show() \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "brainiac", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.21" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/BrainIAC/utils.py b/src/BrainIAC/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ad4540048700a4ab4343ea62e929d45dd00ca63c --- /dev/null +++ b/src/BrainIAC/utils.py @@ -0,0 +1,47 @@ +import yaml +import os +import torch +import random +import numpy as np + +class BaseConfig: + def __init__(self): + config_path = os.path.join(os.path.dirname(__file__), 'config.yml') + with open(config_path, 'r') as file: + self.config = yaml.safe_load(file) + + self.setup_environment() + + def setup_environment(self): + seed = 42 + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + + os.environ['CUDA_VISIBLE_DEVICES'] = self.config["gpu"]["visible_device"] + self.device = torch.device(self.config["gpu"]["device"]) #torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + torch.set_float32_matmul_precision("medium") + + def custom_collate(self, batch): + """Handles variable size of the scans and pads the sequence dimension.""" + images = [item['image'] for item in batch] + labels = [item['label'] for item in batch] + + max_len = self.config["data"]["collate"] # Single scan input + padded_images = [] + + for img in images: + pad_size = max_len - img.shape[0] + if pad_size > 0: + padding = torch.zeros((pad_size,) + img.shape[1:]) + img_padded = torch.cat([img, padding], dim=0) + padded_images.append(img_padded) + else: + padded_images.append(img) + + return {"image": torch.stack(padded_images, dim=0), "label": torch.stack(labels)} + + def get_config(self): + return self.config \ No newline at end of file