diff --git a/DeepGraft/CTMIL_feat_norm_rest.yaml b/DeepGraft/CTMIL_feat_norm_rest.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c3a92870d0998a42c6b8ebdb8c41932d08161df8
--- /dev/null
+++ b/DeepGraft/CTMIL_feat_norm_rest.yaml
@@ -0,0 +1,55 @@
+General:
+    comment: 
+    seed: 2021
+    fp16: True
+    amp_level: O2
+    precision: 16-mixed 
+    multi_gpu_mode: dp
+    gpus: [0]
+    epochs: &epoch 1000 
+    grad_acc: 2
+    frozen_bn: False
+    patience: 100
+    server: train #train #test
+    log_path: /home/ylan/workspace/TransMIL-DeepGraft/logs/
+
+Data:
+    dataset_name: custom
+    data_shuffle: False
+    mixup: True
+    aug: True
+    cache: False
+    data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
+    fold: 1
+    nfold: 3
+    cross_val: False
+
+    train_dataloader:
+        batch_size: 128
+        num_workers: 4
+
+    test_dataloader:
+        batch_size: 1
+        num_workers: 4
+
+Model:
+    name: CTMIL
+    n_classes: 2
+    backbone: features
+    in_features: 2048
+    out_features: 512
+
+
+Optimizer:
+    opt: radam
+    lr: 0.002
+    opt_eps: null 
+    opt_betas: null
+    momentum: null 
+    weight_decay: 0.01
+
+Loss:
+    base_loss: CrossEntropyLoss
+    
+
diff --git a/DeepGraft/Resnet50_classic_norm_rest.yaml b/DeepGraft/Resnet50_classic_norm_rest.yaml
index a894a793234af65730018f38bae704c71d1184de..877ee05aad85566e883b27825258295d0511135b 100644
--- a/DeepGraft/Resnet50_classic_norm_rest.yaml
+++ b/DeepGraft/Resnet50_classic_norm_rest.yaml
@@ -20,7 +20,7 @@ Data:
     aug: True
     cache: False
     data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
-    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_norm_rest_val_1.json'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
     fold: 1
     nfold: 3
     cross_val: False
@@ -42,7 +42,7 @@ Model:
 
 
 Optimizer:
-    opt: lookahead_radam
+    opt: radam
     lr: 0.001
     opt_eps: null 
     opt_betas: null
diff --git a/DeepGraft/Resnet50_feat_norm_rest.yaml b/DeepGraft/Resnet50_feat_norm_rest.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9ebe03b30dad9083a7d71fc66814bac9bf6c4734
--- /dev/null
+++ b/DeepGraft/Resnet50_feat_norm_rest.yaml
@@ -0,0 +1,55 @@
+General:
+    comment: 
+    seed: 2021
+    fp16: True
+    amp_level: O2
+    precision: 16-mixed
+    multi_gpu_mode: ddp
+    gpus: [0, 1]
+    epochs: &epoch 500 
+    grad_acc: 2
+    frozen_bn: False
+    patience: 50
+    server: train #train #test
+    log_path: /home/ylan/workspace/TransMIL-DeepGraft/logs/
+
+Data:
+    dataset_name: custom
+    data_shuffle: False
+    mixup: True
+    aug: True
+    cache: False
+    data_dir: '/raid/ylan/data/DeepGraft/224_256uM_annotated/'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
+    fold: 1
+    nfold: 3
+    cross_val: False
+
+    train_dataloader:
+        batch_size: 8
+        num_workers: 1
+
+    test_dataloader:
+        batch_size: 1
+        num_workers: 4
+
+Model:
+    name: resnet50
+    n_classes: 2
+    backbone: features
+    in_features: 768
+    out_features: 512
+
+
+Optimizer:
+    opt: radam
+    lr: 0.001
+    opt_eps: null 
+    opt_betas: null
+    momentum: null 
+    weight_decay: 0.01
+
+Loss:
+    base_loss: CrossEntropyLoss
+    
+
diff --git a/DeepGraft/TransMIL_feat_no_other.yaml b/DeepGraft/TransMIL_feat_no_other.yaml
index 72642137ba796f981128d58b492f8b91726defab..530651c9b5860738e9ba52379e2b07c3229d674a 100644
--- a/DeepGraft/TransMIL_feat_no_other.yaml
+++ b/DeepGraft/TransMIL_feat_no_other.yaml
@@ -6,7 +6,7 @@ General:
     precision: 16-mixed 
     multi_gpu_mode: dp
     gpus: [0]
-    epochs: &epoch 500 
+    epochs: &epoch 1000 
     grad_acc: 2
     frozen_bn: False
     patience: 50
@@ -15,6 +15,8 @@ General:
 
 Data:
     dataset_name: custom
+    feature_extractor: histoencoder
+    
     data_shuffle: False
     mixup: True
     aug: True
@@ -37,13 +39,13 @@ Model:
     name: TransMIL
     n_classes: 5
     backbone: features
-    in_features: 2048
-    out_features: 512
+    in_features: 384
+    out_features: 384
 
 
 Optimizer:
     opt: radam
-    lr: 0.002
+    lr: 0.00001
     opt_eps: null 
     opt_betas: null
     momentum: null 
diff --git a/DeepGraft/TransMIL_feat_norm_rej_rest.yaml b/DeepGraft/TransMIL_feat_norm_rej_rest.yaml
index 6af5de7682b38bd66e12cdd59be851daa41c7804..96655af5881576854dc4c2e47454b55878122e85 100644
--- a/DeepGraft/TransMIL_feat_norm_rej_rest.yaml
+++ b/DeepGraft/TransMIL_feat_norm_rej_rest.yaml
@@ -15,10 +15,12 @@ General:
 
 Data:
     dataset_name: custom
+    feature_extractor: histoencoder
     data_shuffle: False
     mixup: True
     aug: True
     cache: False
+    bag_size: 500
     data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
     label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rej_rest_ext.json'
     fold: 1
@@ -26,7 +28,7 @@ Data:
     cross_val: False
 
     train_dataloader:
-        batch_size: 50
+        batch_size: 64
         num_workers: 4
 
     test_dataloader:
@@ -37,13 +39,13 @@ Model:
     name: TransMIL
     n_classes: 3
     backbone: features
-    in_features: 2048
-    out_features: 512
+    in_features: 384
+    out_features: 384
 
 
 Optimizer:
-    opt: lookahead_radam
-    lr: 0.002
+    opt: radam
+    lr: 0.0001
     opt_eps: null 
     opt_betas: null
     momentum: null 
diff --git a/DeepGraft/TransMIL_feat_norm_rest-gloms.yaml b/DeepGraft/TransMIL_feat_norm_rest-gloms.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..687d72a5ded0f1f83463a35d4041d146467188e0
--- /dev/null
+++ b/DeepGraft/TransMIL_feat_norm_rest-gloms.yaml
@@ -0,0 +1,56 @@
+General:
+    comment: 
+    seed: 2021
+    fp16: True
+    amp_level: O2
+    precision: 16-mixed
+    multi_gpu_mode: ddp
+    gpus: [0, 1]
+    epochs: &epoch 1000 
+    grad_acc: 2
+    frozen_bn: False
+    patience: 50
+    server: train #train #test
+    log_path: /home/ylan/workspace/TransMIL-DeepGraft/logs/
+
+Data:
+    dataset_name: custom
+    data_shuffle: False
+    mixup: True
+    aug: True
+    cache: False
+    bag_size: 20
+    data_dir: '/home/ylan/data/DeepGraft/224_256uM_gloms/'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
+    fold: 1
+    nfold: 3
+    cross_val: False
+
+    train_dataloader:
+        batch_size: 100
+        num_workers: 4
+
+    test_dataloader:
+        batch_size: 1
+        num_workers: 4
+
+Model:
+    name: TransMIL
+    n_classes: 2
+    backbone: features
+    in_features: 2048
+    out_features: 256
+
+
+Optimizer:
+    opt: radam
+    lr: 0.0005
+    opt_eps: null 
+    opt_betas: null
+    momentum: null 
+    weight_decay: 0.01
+
+Loss:
+    base_loss: CrossEntropyLoss
+    
+
diff --git a/DeepGraft/TransMIL_feat_norm_rest.yaml b/DeepGraft/TransMIL_feat_norm_rest.yaml
index 598adf67d7845b38844e7705da4f59911979b4fb..a5a6dc75c0a89858b271cc5a998b4d0b6cec37ac 100644
--- a/DeepGraft/TransMIL_feat_norm_rest.yaml
+++ b/DeepGraft/TransMIL_feat_norm_rest.yaml
@@ -9,24 +9,27 @@ General:
     epochs: &epoch 1000 
     grad_acc: 2
     frozen_bn: False
-    patience: 50
+    patience: 100
     server: train #train #test
     log_path: /home/ylan/workspace/TransMIL-DeepGraft/logs/
 
 Data:
     dataset_name: custom
+    feature_extractor: retccl
     data_shuffle: False
-    mixup: True
+    mixup: False
     aug: True
-    cache: False
-    data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
+    cache: True
+    bag_size: 200
+    # data_dir: '/homeStor1/ylan/data/DeepGraft/224_1024uM_annotated/'
+    data_dir: '/homeStor1/ylan/data/DeepGraft/512_256uM_annotated/'
     label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
     fold: 1
     nfold: 3
     cross_val: False
 
     train_dataloader:
-        batch_size: 50
+        batch_size: 64
         num_workers: 4
 
     test_dataloader:
@@ -37,8 +40,8 @@ Model:
     name: TransMIL
     n_classes: 2
     backbone: features
-    in_features: 768
-    out_features: 512
+    # in_features: 384
+    # out_features: 384
 
 
 Optimizer:
diff --git a/DeepGraft/TransMIL_feat_rejections.yaml b/DeepGraft/TransMIL_feat_rejections.yaml
index 71dbc8da6f0315938c2e92edc771668927143fd6..223f464c2cd2945e446b2054e9d57165428b603f 100644
--- a/DeepGraft/TransMIL_feat_rejections.yaml
+++ b/DeepGraft/TransMIL_feat_rejections.yaml
@@ -18,9 +18,9 @@ Data:
     data_shuffle: False
     mixup: True
     aug: True
-    cache: False
+    cache: True
     data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
-    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_rejections_mixin_1.json'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_rejections.json'
     fold: 1
     nfold: 3
     cross_val: False
@@ -42,7 +42,7 @@ Model:
 
 
 Optimizer:
-    opt: lookahead_radam
+    opt: radam
     lr: 0.002
     opt_eps: null 
     opt_betas: null
diff --git a/DeepGraft/TransMIL_resnet50_norm_rest.yaml b/DeepGraft/TransMIL_resnet50_norm_rest.yaml
index 0511d268534c6e2a3af203a3663acc13dab7b486..8c51311924306ab785ccd8722ba01b486ad3e874 100644
--- a/DeepGraft/TransMIL_resnet50_norm_rest.yaml
+++ b/DeepGraft/TransMIL_resnet50_norm_rest.yaml
@@ -16,8 +16,8 @@ General:
 Data:
     dataset_name: custom
     data_shuffle: False
-    data_dir: '/home/ylan/data/DeepGraft/224_128um_v2/'
-    label_file: '/home/ylan/DeepGraft/training_tables/dg_limit_100_split_PAS_HE_Jones_norm_rest_RA_RU.json'
+    data_dir: '/home/ylan/data/DeepGraft/224_256uM_annotated/'
+    label_file: '/home/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
     fold: 1
     nfold: 3
     cross_val: False
@@ -39,7 +39,7 @@ Model:
 
 
 Optimizer:
-    opt: lookahead_radam
+    opt: radam
     lr: 0.0002
     opt_eps: null 
     opt_betas: null
diff --git a/DeepGraft/TransformerMIL_feat_norm_rest.yaml b/DeepGraft/TransformerMIL_feat_norm_rest.yaml
index 85626299bff9d2964403be2b72c814208575c1d3..2bab970b4691691bbe2e6a07f2568414f45b51b6 100644
--- a/DeepGraft/TransformerMIL_feat_norm_rest.yaml
+++ b/DeepGraft/TransformerMIL_feat_norm_rest.yaml
@@ -3,7 +3,7 @@ General:
     seed: 2021
     fp16: True
     amp_level: O2
-    precision: 16 
+    precision: 16-mixed 
     multi_gpu_mode: dp
     gpus: [0]
     epochs: &epoch 1000 
@@ -26,7 +26,7 @@ Data:
     cross_val: False
 
     train_dataloader:
-        batch_size: 25
+        batch_size: 128
         num_workers: 4
 
     test_dataloader:
@@ -37,7 +37,7 @@ Model:
     name: TransformerMIL
     n_classes: 2
     backbone: features
-    in_features: 2048
+    in_features: 768
     out_features: 512
 
 
diff --git a/DeepGraft/Vit_classic_norm_rest.yaml b/DeepGraft/Vit_classic_norm_rest.yaml
index 8ad003257c18b357c785aa594d0a1e6aee81fcd2..5a790bdfb56aa4297f3c40caf7e37ac5d966e0f7 100644
--- a/DeepGraft/Vit_classic_norm_rest.yaml
+++ b/DeepGraft/Vit_classic_norm_rest.yaml
@@ -3,7 +3,7 @@ General:
     seed: 2021
     fp16: True
     amp_level: O2
-    precision: 16
+    precision: 16-mixed
     multi_gpu_mode: ddp
     gpus: [0, 1]
     epochs: &epoch 500 
@@ -16,6 +16,7 @@ General:
 Data:
     dataset_name: custom
     data_shuffle: False
+    feature_extractor: None
     mixup: True
     aug: True
     cache: False
@@ -26,7 +27,7 @@ Data:
     cross_val: False
 
     train_dataloader:
-        batch_size: 200 
+        batch_size: 3000 
         num_workers: 4
 
     test_dataloader:
diff --git a/code/datasets/__init__.py b/code/datasets/__init__.py
index 10ac41c45fb3fb1f2acd52c1c171891d24c8f546..2af3682e57a38d943db76ab5674aeaa5c481dd76 100644
--- a/code/datasets/__init__.py
+++ b/code/datasets/__init__.py
@@ -3,3 +3,4 @@ from .jpg_dataloader import JPGMILDataloader
 from .feature_dataloader import FeatureBagLoader
 from .data_interface import MILDataModule
 from .fast_tensor_dl import FastTensorDataLoader
+from .local_feature_dataloader import LocalFeatureBagLoader
diff --git a/code/datasets/__pycache__/__init__.cpython-39.pyc b/code/datasets/__pycache__/__init__.cpython-39.pyc
index 295f1815d67c30eabb235a28310f4489baceeef4..fa67b0f29211e56552497630984d358b6a4aef1e 100644
Binary files a/code/datasets/__pycache__/__init__.cpython-39.pyc and b/code/datasets/__pycache__/__init__.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/classic_jpg_dataloader.cpython-39.pyc b/code/datasets/__pycache__/classic_jpg_dataloader.cpython-39.pyc
index ba069cbf046231815836ffd726592c7a8d6e6ec9..2bd1a8baf618ae1eb07192878f0d354482abadc0 100644
Binary files a/code/datasets/__pycache__/classic_jpg_dataloader.cpython-39.pyc and b/code/datasets/__pycache__/classic_jpg_dataloader.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/custom_resnet50.cpython-39.pyc b/code/datasets/__pycache__/custom_resnet50.cpython-39.pyc
index e83a6cd9d528d97ded3c4ae4e451d5cad70e63ed..bf58e430f89a8827bdea350dd5ce08d357a54642 100644
Binary files a/code/datasets/__pycache__/custom_resnet50.cpython-39.pyc and b/code/datasets/__pycache__/custom_resnet50.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/data_interface.cpython-39.pyc b/code/datasets/__pycache__/data_interface.cpython-39.pyc
index 4b75185ffe785a930b52a45603beef6de633c028..96e5f65e605f6fbd4c86e029ee265c99a59752a1 100644
Binary files a/code/datasets/__pycache__/data_interface.cpython-39.pyc and b/code/datasets/__pycache__/data_interface.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/feature_dataloader.cpython-39.pyc b/code/datasets/__pycache__/feature_dataloader.cpython-39.pyc
index 3cfbb24377e353d44c206aa9b69b511bedcf57a7..208f4365a562690675c07fc23dd6470276c37447 100644
Binary files a/code/datasets/__pycache__/feature_dataloader.cpython-39.pyc and b/code/datasets/__pycache__/feature_dataloader.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/jpg_dataloader.cpython-39.pyc b/code/datasets/__pycache__/jpg_dataloader.cpython-39.pyc
index d0ae46302a1148a8c051d964b078136442e69810..d37d178d0ade5a727ac55666047ebf453fb9870d 100644
Binary files a/code/datasets/__pycache__/jpg_dataloader.cpython-39.pyc and b/code/datasets/__pycache__/jpg_dataloader.cpython-39.pyc differ
diff --git a/code/datasets/__pycache__/local_feature_dataloader.cpython-39.pyc b/code/datasets/__pycache__/local_feature_dataloader.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c9f29351da78bdaa951a6ae198b1f6d0dbd18067
Binary files /dev/null and b/code/datasets/__pycache__/local_feature_dataloader.cpython-39.pyc differ
diff --git a/code/datasets/classic_jpg_dataloader.py b/code/datasets/classic_jpg_dataloader.py
index 1e127562a205f907aee0aa93426847237e460d6a..4f54932f53659dd62cd9f3216ca6eb901f026776 100644
--- a/code/datasets/classic_jpg_dataloader.py
+++ b/code/datasets/classic_jpg_dataloader.py
@@ -22,10 +22,11 @@ from .utils import myTransforms
 from transformers import ViTFeatureExtractor
 import torchvision.models as models
 import torch.nn as nn
+import random
 
 
 class JPGBagLoader(data_utils.Dataset):
-    def __init__(self, file_path, label_path, mode, n_classes, data_cache_size=100, max_bag_size=1000, cache=False, mixup=False, aug=False, model=''):
+    def __init__(self, file_path, label_path, mode, n_classes, data_cache_size=100, max_bag_size=1000, cache=False, mixup=False, aug=False, model='', **kargs):
         super().__init__()
 
         self.data_info = []
@@ -75,7 +76,7 @@ class JPGBagLoader(data_utils.Dataset):
                             # self.labels.append(int(y))
                             for patch in x_path.iterdir():
                                 self.files.append((patch, x_name, y))
-
+        random.shuffle(self.files)
         # with open(self.label_path, 'r') as f:
         #     temp_slide_label_dict = json.load(f)[mode]
         #     print(len(temp_slide_label_dict))
@@ -292,11 +293,11 @@ if __name__ == '__main__':
 
     home = Path.cwd().parts[1]
     # train_csv = f'/{home}/ylan/DeepGraft_project/code/debug_train.csv'
-    data_root = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated'
+    data_root = f'/raid/ylan/data/DeepGraft/224_256uM_annotated'
     # data_root = f'/{home}/ylan/DeepGraft/dataset/hdf5/256_256um_split/'
     # label_path = f'/{home}/ylan/DeepGraft_project/code/split_PAS_bin.json'
     # label_path = f'/{home}/ylan/DeepGraft/training_tables/split_debug.json'
-    label_path = f'/{home}/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_norm_rest.json'
+    label_path = '/homeStor1/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
     # output_dir = f'/{data_root}/debug/augments'
     # os.makedirs(output_dir, exist_ok=True)
 
@@ -325,16 +326,16 @@ if __name__ == '__main__':
     #     param.requires_grad = False
     # model_ft.to(device)
 
-    model_ft = models.resnet50(weights='IMAGENET1K_V1')
+    # model_ft = models.resnet50(weights='IMAGENET1K_V1')
 
     
-    ct = 0
-    for child in model_ft.children():
-        ct += 1
-        if ct < len(list(model_ft.children())) - 3:
-            for parameter in child.parameters():
-                parameter.requires_grad=False
-    model_ft.fc = nn.Linear(model_ft.fc.in_features, 2)
+    # ct = 0
+    # for child in model_ft.children():
+    #     ct += 1
+    #     if ct < len(list(model_ft.children())) - 3:
+    #         for parameter in child.parameters():
+    #             parameter.requires_grad=False
+    # model_ft.fc = nn.Linear(model_ft.fc.in_features, 2)
 
     # print(model_ft)
 
@@ -351,7 +352,8 @@ if __name__ == '__main__':
         if c >= 1000:
             break
         bag, label, (name, batch_names, patient) = item
-        print(bag.shape)
+        # print(bag.shape)
+        print(name)
         # print(name)
         # print(batch_names)
         # print(patient)
diff --git a/code/datasets/custom_jpg_dataloader.py b/code/datasets/custom_jpg_dataloader.py
index 8e1ce3dc47fc05b0b1008817efc80f8813edeb87..ed73299377fe2bd395543e51c0cb2719fff93b9d 100644
--- a/code/datasets/custom_jpg_dataloader.py
+++ b/code/datasets/custom_jpg_dataloader.py
@@ -251,7 +251,6 @@ class JPGMILDataloader(data.Dataset):
     
     def _add_data_infos(self, file_path, cache, slide_patient_dict):
 
-        
         wsi_name = Path(file_path).stem
         if wsi_name in self.slideLabelDict:
             # if wsi_name[:2] != 'RU': #skip RU because of container problems in dataset
diff --git a/code/datasets/custom_resnet50.py b/code/datasets/custom_resnet50.py
index 0c3f33518bc53a1611b94767bc50fbbb169154d0..850d78887642566fcf6c813b4c1c870280ff3171 100644
--- a/code/datasets/custom_resnet50.py
+++ b/code/datasets/custom_resnet50.py
@@ -57,8 +57,10 @@ class ResNet_Baseline(nn.Module):
     def __init__(self, block, layers):
         self.inplanes = 64
         super(ResNet_Baseline, self).__init__()
-        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
+        self.conv1 = nn.Conv2d(2048, 64, kernel_size=7, stride=2, padding=3,
                                bias=False)
+        # self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
+        #                        bias=False)
         self.bn1 = nn.BatchNorm2d(64)
         self.relu = nn.ReLU(inplace=True)
         self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
diff --git a/code/datasets/dali_dataloader.py b/code/datasets/dali_dataloader.py
index a5efe96fc66f9940ed6ee387dc50d24d01a63cdd..0546766719f210ba8ddc042ebb8821c2b55572db 100644
--- a/code/datasets/dali_dataloader.py
+++ b/code/datasets/dali_dataloader.py
@@ -1,12 +1,12 @@
-import nvidia.dali as dali
-from nvidia.dali import pipeline_def
+# import nvidia.dali as dali
+# from nvidia.dali import pipeline_def
 from nvidia.dali.pipeline import Pipeline
 import nvidia.dali.fn as fn
 # import nvidia.dali.fn.readers.file as file
 from nvidia.dali.fn.decoders import image, image_random_crop
 
 import nvidia.dali.types as types
-from nvidia.dali.plugin.pytorch import DALIClassificationIterator, LastBatchPolicy
+from nvidia.dali.plugin.pytorch import DALIClassificationIterator, LastBatchPolicy, DALIGenericIterator
 
 from pathlib import Path
 import json
@@ -41,7 +41,7 @@ class ExternalInputIterator(object):
         self.empty_slides = []
 
         home = Path.cwd().parts[1]
-        slide_patient_dict_path = f'/{home}/ylan/DeepGraft/training_tables/slide_patient_dict.json'
+        slide_patient_dict_path = f'/homeStor1/ylan/data/DeepGraft/training_tables/slide_patient_dict.json'
         with open(slide_patient_dict_path, 'r') as f:
             self.slidePatientDict = json.load(f)
 
@@ -77,8 +77,8 @@ class ExternalInputIterator(object):
 
         self.n = len(self.files)
 
-        test_data_root = os.environ['DALI_EXTRA_PATH']
-        jpeg_file = os.path.join(test_data_root, 'db', 'single', 'jpeg', '510', 'ship-1083562_640.jpg')
+        # test_data_root = os.environ['DALI_EXTRA_PATH']
+        # jpeg_file = os.path.join(test_data_root, 'db', 'single', 'jpeg', '510', 'ship-1083562_640.jpg')
 
     def __iter__(self):
         self.i = 0
@@ -216,21 +216,40 @@ def ExternalSourcePipeline(batch_size, num_threads, device_id, external_data):
 
 # training_dataloader = DALIClassificationIterator(pipelines=training_pipeline, reader_name='Reader', last_batch_policy=LastBatchPolicy.PARTIAL, auto_reset=True)
 # validation_pipeline = DALIClassificationIterator(pipelines=validation_pipeline, reader_name='Reader', last_batch_policy=LastBatchPolicy.PARTIAL, auto_reset=True)
+@pipeline_def(num_threads=4, device_id=self.trainer.local.rank)
+def get_dali_pipeline(images_dir):
+    images, _ = fn.readers.file(file_root=images_dir, random_shuffle=True, name="Reader")
+    # decode data on the GPU
+    images = fn.decoders.image_random_crop(images, device="mixed", output_type=types.RGB)
+    # the rest of processing happens on the GPU as well
+    images = fn.resize(images, resize_x=256, resize_y=256)
+    images = fn.crop_mirror_normalize(
+        images,
+        crop_h=224,
+        crop_w=224,
+        mean=[0.485 * 255, 0.456 * 255, 0.406 * 255],
+        std=[0.229 * 255, 0.224 * 255, 0.225 * 255],
+        mirror=fn.random.coin_flip(),
+    )
+    return images
 
 if __name__ == '__main__':
 
     home = Path.cwd().parts[1]
-    file_path = f'/{home}/ylan/data/DeepGraft/224_128um_v2'
-    label_path = f'/{home}/ylan/DeepGraft/training_tables/dg_limit_20_split_PAS_HE_Jones_norm_rest.json'
-    eii = ExternalInputIterator(file_path, label_path, mode="train", n_classes=2, device_id=0, num_gpus=1)
-
-    pipe = ExternalSourcePipeline(batch_size=1, num_threads=2, device_id = 0,
-                              external_data = eii)
-    pii = DALIClassificationIterator(pipe, last_batch_padded=True, last_batch_policy=LastBatchPolicy.PARTIAL)
-
-    for e in range(3):
-        for i, data in enumerate(pii):
-            # print(data)
-            print("epoch: {}, iter {}, real batch size: {}".format(e, i, len(data[0]["data"])))
-        pii.reset()
+    file_path = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated'
+    label_path = f'/{home}/ylan/data/DeepGraft/training_tables/dg_limit_20_split_PAS_HE_Jones_norm_rest.json'
+
+    train_dataloader = DALIGenericIterator([get_dali_pipeline(batch_size=16)], ['data'])
+    # eii = ExternalInputIterator(file_path, label_path, mode="train", n_classes=2, device_id=0, num_gpus=1)
+
+    # pipe = ExternalSourcePipeline(batch_size=1, num_threads=2, device_id = 0,
+    #                         external_data = eii)
+    # pii = DALIClassificationIterator(pipe, last_batch_padded=True, last_batch_policy=LastBatchPolicy.PARTIAL)
+
+    # for e in range(3):
+    #     for i, data in enumerate(pii):
+    #         # print(data)
+    #         print("epoch: {}, iter {}, real batch size: {}".format(e, i, len(data[0]["data"])))
+    #     pii.reset()
+
             
diff --git a/code/datasets/data_interface.py b/code/datasets/data_interface.py
index 41dd2cea391e869b037b4a60ae04332473d7ef10..c62e041a04b8a963c12694f30eb775d1ceeedc10 100644
--- a/code/datasets/data_interface.py
+++ b/code/datasets/data_interface.py
@@ -16,6 +16,7 @@ from .jpg_dataloader import JPGMILDataloader
 from .classic_jpg_dataloader import JPGBagLoader
 from .zarr_feature_dataloader_simple import ZarrFeatureBagLoader
 from .feature_dataloader import FeatureBagLoader
+from .local_feature_dataloader import LocalFeatureBagLoader
 from pathlib import Path
 # from transformers import AutoFeatureExtractor
 from torchsampler import ImbalancedDatasetSampler
@@ -124,7 +125,7 @@ import torch
 
 class MILDataModule(pl.LightningDataModule):
 
-    def __init__(self, data_root: str, label_path: str, model_name: str, batch_size: int=1, num_workers: int=8, n_classes=2, cache: bool=True, use_features=False, train_classic=False, mixup=False, aug=False, fine_tune=False, *args, **kwargs):
+    def __init__(self, data_root: str, label_path: str, model_name: str, batch_size: int=1, num_workers: int=8, n_classes=2, cache: bool=True, use_features=False, train_classic=False, mixup=False, aug=False, fine_tune=False, bag_size=500, *args, **kwargs):
         super().__init__()
         self.data_root = data_root
         self.label_path = label_path
@@ -142,11 +143,15 @@ class MILDataModule(pl.LightningDataModule):
         self.aug = aug
         self.train_classic = train_classic
         self.fine_tune = fine_tune
-        self.max_bag_size = 1000
+        self.max_bag_size = bag_size
         self.model_name = model_name
         self.use_features = use_features
+        self.in_features = kwargs['in_features']
+        self.feature_extractor = kwargs['feature_extractor']
+        # if self.feature_
+        # elif self.feature_extractor == 'histoencoder':
+        self.fe_name = f'FEATURES_{self.feature_extractor.upper()}_{self.in_features}'
 
-        
 
         self.class_weight = []
         self.cache = cache
@@ -159,23 +164,25 @@ class MILDataModule(pl.LightningDataModule):
         else: 
             self.base_dataloader = FeatureBagLoader
             # self.cache = True
-
-
+        if model_name == 'resnet50' or model_name == 'CTMIL':
+            self.base_dataloader = LocalFeatureBagLoader
 
     def setup(self, stage: Optional[str] = None) -> None:
         home = Path.cwd().parts[1]
         # print('batch size: ', self.batch_size)
         # print('valid_data')
-        self.valid_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='val', n_classes=self.n_classes, cache=self.cache, model=self.model_name)
+        
+        self.valid_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='val', n_classes=self.n_classes, cache=self.cache, model=self.model_name, feature_extractor=self.fe_name) #, max_bag_size=self.max_bag_size
         if stage in (None, 'fit'):
             # print('self.fine_tune', self.fine_tune)
             if self.fine_tune:
-                self.train_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='fine_tune', n_classes=self.n_classes, cache=self.cache, mixup=self.mixup, aug=self.aug, model=self.model_name)
+                self.train_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='fine_tune', n_classes=self.n_classes, cache=self.cache, mixup=self.mixup, aug=self.aug, model=self.model_name, feature_extractor=self.fe_name, max_bag_size=self.max_bag_size) #, max_bag_size=self.max_bag_size
             else:
-                self.train_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='train', n_classes=self.n_classes, cache=self.cache, mixup=self.mixup, aug=self.aug, model=self.model_name)
+                self.train_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='train', n_classes=self.n_classes, cache=self.cache, mixup=self.mixup, aug=self.aug, model=self.model_name, feature_extractor=self.fe_name) #, max_bag_size=self.max_bag_size
             # self.valid_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='val', n_classes=self.n_classes, cache=self.cache, model=self.model_name)
 
             # dataset = JPGMILDataloader(self.data_root, label_path=self.label_path, mode='train', n_classes=self.n_classes)
+            # print(self.base_dataloader)
             print('Train Data: ', len(self.train_data))
             # print('Val Data: ', len(self.valid_data))
             # a = int(len(dataset)* 0.8)
@@ -188,7 +195,7 @@ class MILDataModule(pl.LightningDataModule):
 
         if stage in (None, 'test'):
             
-            self.test_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='test', n_classes=self.n_classes, cache=False, model=self.model_name, mixup=False, aug=False)
+            self.test_data = self.base_dataloader(self.data_root, label_path=self.label_path, mode='test', n_classes=self.n_classes, cache=False, model=self.model_name, mixup=False, aug=False, feature_extractor=self.fe_name) #, max_bag_size=self.max_bag_size
 
         return super().setup(stage=stage)
 
@@ -200,20 +207,45 @@ class MILDataModule(pl.LightningDataModule):
         if self.train_classic or not self.use_features:
             return DataLoader(self.train_data, batch_size = self.batch_size, num_workers=self.num_workers) #batch_transforms=self.transform, pseudo_batch_dim=True, 
         else:
-            return DataLoader(self.train_data,  batch_size = self.batch_size, sampler=ImbalancedDatasetSampler(self.train_data), num_workers=self.num_workers) #batch_transforms=self.transform, pseudo_batch_dim=True, 
+            return DataLoader(self.train_data,  batch_size = self.batch_size, sampler=ImbalancedDatasetSampler(self.train_data), num_workers=self.num_workers, collate_fn=self.simple_collate) #batch_transforms=self.transform, pseudo_batch_dim=True, 
             # return DataLoader(self.train_data,  batch_size = self.batch_size, num_workers=self.num_workers) #batch_transforms=self.transform, pseudo_batch_dim=True, 
         #sampler=ImbalancedDatasetSampler(self.train_data)
     def val_dataloader(self) -> DataLoader:
         if self.train_classic:
             return DataLoader(self.valid_data, batch_size = self.batch_size, num_workers=self.num_workers)
         else:
-            return DataLoader(self.valid_data, batch_size = 1, num_workers=self.num_workers)
+            return DataLoader(self.valid_data, batch_size = 1, sampler=ImbalancedDatasetSampler(self.valid_data), num_workers=self.num_workers)
     
     def test_dataloader(self) -> DataLoader:
         if self.train_classic:
             return DataLoader(self.test_data, batch_size = self.batch_size, num_workers=self.num_workers)
         else: return DataLoader(self.test_data, batch_size = 1, num_workers=self.num_workers)
 
+    def simple_collate(self, data):
+        # print(data[0])
+        bags = [i[0] for i in data]
+        labels = [i[1] for i in data]
+        name = [i[2][0] for i in data]
+        patient = [i[2][1] for i in data]
+        bags = torch.stack(bags)
+        labels = torch.Tensor(np.stack(labels, axis=0)).long()
+        return bags, labels, (name, patient)
+
+    def custom_collate_fn(self, batch):
+        # out_batch = [i for i in batch]
+        # for i in range(len(batch)):
+        # x = torch.stack(list(batch))
+
+        out_batch = [i[0] for i in batch]
+        labels = [i[1] for i in batch]
+        wsi_name = [i[2][0] for i in batch]
+        batch_coords = [i[2][1] for i in batch]
+        patient = [i[2][2] for i in batch]
+
+        # print(x.shape)
+        return out_batch, labels, (wsi_name, batch_coords, patient)
+            
+
     def get_weights(self, dataset):
 
         label_count = [0]*self.n_classes
diff --git a/code/datasets/feature_dataloader.py b/code/datasets/feature_dataloader.py
index d6387ef2748afc2a3430a1ceb3383d05647678aa..bd4777687d3f0c66e9bf945af2d81c9ee64f397f 100644
--- a/code/datasets/feature_dataloader.py
+++ b/code/datasets/feature_dataloader.py
@@ -23,7 +23,7 @@ import h5py
 
 
 class FeatureBagLoader(data.Dataset):
-    def __init__(self, file_path, label_path, mode, n_classes, model='None',cache=False, mixup=False, aug=False, mix_res=False, data_cache_size=5000, max_bag_size=1000):
+    def __init__(self, file_path, label_path, mode, n_classes, model='None',cache=False, mixup=False, aug=False, mix_res=False, data_cache_size=5000, max_bag_size=1000, **kwargs):
         super().__init__()
 
         self.data_info = []
@@ -43,11 +43,19 @@ class FeatureBagLoader(data.Dataset):
         self.empty_slides = []
         self.corrupt_slides = []
         self.cache = cache
+        # if self.mode == 'test':
+            # self.cache = False
         self.mixup = mixup
         self.aug = aug
         # self.file_path_mix = self.file_path.replace('256', '1024')
         self.missing = []
         self.use_1024 = False
+        # print(kwargs.keys())
+        if 'feature_extractor' in kwargs.keys():
+            self.feature_extractor = kwargs['feature_extractor']
+            # print('self.feature_extractor: ', self.feature_extractor)
+            # self.fe_name = f'FEATURES_{self.feature_extractor.upper()}_{self.in_features}'
+        # print(self.feature_extractor)
 
         # print('Using FeatureBagLoader: ', self.mode)
 
@@ -64,17 +72,23 @@ class FeatureBagLoader(data.Dataset):
             if self.mode == 'fine_tune':
                 temp_slide_label_dict = json_dict['train'] + json_dict['test_mixin']
             else: temp_slide_label_dict = json_dict[self.mode]
-            # print('temp_slide_label_dict:', len(temp_slide_label_dict))
+            # temp_slide_label_dict = json_dict['train']
+            # temp_slide_label_dict = json_dict['train'] + json_dict['test_mixin'] # simulate fine tuning
+            
             for (x,y) in temp_slide_label_dict:
                 
                 # test_path = Path(self.file_path)
                 # if Path(self.file_path) / 
                 # if self.mode != 'test':
+                    # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_RETCCL_2048_HED')
                     # x = x.replace('FEATURES_RETCCL_2048', 'TEST')
                 # else:
                     # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_RESNET50_1024_HED')
-                    # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_RETCCL_2048_HED')
-                x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_CTRANSPATH_768')
+                #     # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_HISTOENCODER_384')
+                
+                if self.feature_extractor:
+                    x = x.replace('FEATURES_RETCCL_2048', self.feature_extractor)
+                # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_HISTOENCODER_384')
                 # else:
                     # x = x.replace('Aachen_Biopsy_Slides', 'Aachen_Biopsy_Slides_extended')
                 x_name = Path(x).stem
@@ -293,6 +307,32 @@ class FeatureBagLoader(data.Dataset):
             wsi_name = self.wsi_names[index]
             batch_coords = self.coords[index]
             patient = self.patients[index]
+            if self.mode == 'train' or self.mode == 'fine_tune':
+                bag_size = bag.shape[0]
+
+                bag_idxs = torch.randperm(bag_size)[:self.max_bag_size]
+                # bag_idxs = torch.randperm(bag_size)[:int(self.max_bag_size*(1-self.drop_rate))]
+                out_bag = bag[bag_idxs, :]
+                if self.mixup:
+                    out_bag = self.get_mixup_bag(out_bag)
+                    # batch_coords = 
+                if out_bag.shape[0] < self.max_bag_size:
+                    out_bag = torch.cat((out_bag, torch.zeros(self.max_bag_size-out_bag.shape[0], out_bag.shape[1])))
+
+                # shuffle again
+                out_bag_idxs = torch.randperm(out_bag.shape[0])
+                out_bag = out_bag[out_bag_idxs]
+
+
+                # batch_coords only useful for test
+                batch_coords = batch_coords[bag_idxs]
+                return out_bag, label, (wsi_name, patient)
+            else: 
+                
+                # bag_size = bag.shape[0]
+                # bag_idxs = torch.randperm(bag_size)[:self.max_bag_size]
+                # out_bag = bag[bag_idxs, :]
+                out_bag = bag
         else:
             t = self.files[index]
             label = self.labels[index]
@@ -328,6 +368,9 @@ class FeatureBagLoader(data.Dataset):
                 # if out_bag.shape[0] < self.max_bag_size:
                     # out_bag = torch.cat((out_bag, torch.zeros(self.max_bag_size-out_bag.shape[0], out_bag.shape[1])))
                     # out_batch_coords = 
+                # bag_size = bag.shape[0]
+                # bag_idxs = torch.randperm(bag_size)[:self.max_bag_size]
+                # out_bag = bag[bag_idxs, :]
                 out_bag = bag
 
             # print('feature_dataloader: ', out_bag.shape)
@@ -345,6 +388,7 @@ if __name__ == '__main__':
     import time
     # from fast_tensor_dl import FastTensorDataLoader
     from custom_resnet50 import resnet50_baseline
+    from sklearn.decomposition import PCA
     
     home = Path.cwd().parts[1]
     train_csv = f'/{home}/ylan/DeepGraft_project/code/debug_train.csv'
@@ -357,10 +401,10 @@ if __name__ == '__main__':
     # os.makedirs(output_dir, exist_ok=True)
     n_classes = 2
 
-    train_dataset = FeatureBagLoader(data_root, label_path=label_path, mode='train', cache=False, mixup=True, aug=True, n_classes=n_classes)
+    train_dataset = FeatureBagLoader(data_root, label_path=label_path, mode='train', cache=False, mixup=True, aug=True, n_classes=n_classes, max_bag_size=200)
     print('train_dataset: ', len(train_dataset))
 
-    train_dl = DataLoader(train_dataset, batch_size=100, sampler=ImbalancedDatasetSampler(train_dataset)) #
+    train_dl = DataLoader(train_dataset, batch_size=1) #
 
     print('train_dl: ', len(train_dl))
 
@@ -389,21 +433,40 @@ if __name__ == '__main__':
     
 
     # print(dataset.get_labels(np.arange(len(dataset))))
+    pca = PCA(0.95)
+    c = 0
+    label_count = [0] *n_classes
+    epochs = 1
+    # print(len(dl))
+    # start = time.time()
+
+    pca_tensor = []
+
+    for i in range(epochs):
+        start = time.time()
+        for item in tqdm(train_dl): 
+            if c >= 1000:
+                break
+            # print(item)
+            bag, label, (name, patient) = item
+            
+            # print(bag.shape)
+            
+            # print(pca.explained_variance_ratio_)
+            # print(pca.n_components_)
+            # train_pca = pca.transform(x_train)
+            # print(x_train.shape)
+            pca_tensor.append(bag.squeeze())
+            
+            c += 1
+        end = time.time()
+        print('Bag Time: ', end-start)
+
+
 
-    # c = 0
-    # label_count = [0] *n_classes
-    # epochs = 1
-    # # print(len(dl))
-    # # start = time.time()
-    # for i in range(epochs):
-    #     start = time.time()
-    #     for item in tqdm(valid_dl): 
-    #         if c >= 50:
-    #             break
-    #         # print(item)
-    #         bag, label, (name, patient) = item
-    #         c += 1
-    #     end = time.time()
-    #     print('Bag Time: ', end-start)
-
-    
\ No newline at end of file
+    pca_tensor = torch.cat(pca_tensor, dim=0)
+    print(pca_tensor.shape)
+    x_train = pca.fit_transform(pca_tensor.squeeze())
+    print(pca.n_components_)
+    print(pca.components_)
+    print(x_train.shape)
\ No newline at end of file
diff --git a/code/datasets/jpg_dataloader.py b/code/datasets/jpg_dataloader.py
index c45e8cec16ff17c03234d3d2ea025d12c90abb40..2eb2c6cb8ba82d16bc42d4d95c94524e5d9b25da 100644
--- a/code/datasets/jpg_dataloader.py
+++ b/code/datasets/jpg_dataloader.py
@@ -51,11 +51,10 @@ class JPGMILDataloader(data_utils.Dataset):
         # self.patients = []
         home = Path.cwd().parts[1]
 
-        self.slide_patient_dict_path = f'/{home}/ylan/data/DeepGraft/training_tables/slide_patient_dict.json'
+        self.slide_patient_dict_path = f'/{home}/ylan/data/DeepGraft/training_tables/slide_patient_dict_an_ext.json'
         # self.slide_patient_dict_path = Path(self.label_path).parent / 'slide_patient_dict_an.json'
         with open(self.slide_patient_dict_path, 'r') as f:
             self.slide_patient_dict = json.load(f)
-
         # if patients: 
         #     self.slides_to_process = [self.slide_patient_dict[p] for p in patients]
         # elif slides: 
@@ -71,8 +70,10 @@ class JPGMILDataloader(data_utils.Dataset):
             # print(len(temp_slide_label_dict))
 
             for (x,y) in temp_slide_label_dict:
+                
                 if self.mode == 'test':
                     x = x.replace('FEATURES_RETCCL_2048', 'TEST')
+                    
                 else:
                     x = x.replace('FEATURES_RETCCL_2048', 'BLOCKS')
                 # print(x)
@@ -88,6 +89,7 @@ class JPGMILDataloader(data_utils.Dataset):
                                     self.labels += [int(y)]*len(list(x_path.glob('*')))
                                     # self.labels.append(int(y))
                                     self.files.append(x_path)
+                            
                     elif slides: 
                         if x_name in slides:
                             x_path_list = [Path(self.file_path)/x]
@@ -214,10 +216,10 @@ class JPGMILDataloader(data_utils.Dataset):
         
         for tile_path in Path(file_path).iterdir():
             img = Image.open(tile_path)
-            if self.mode == 'train':
+            # if self.mode == 'train':
         
-                # img = self.color_transforms(img)
-                img = self.train_transforms(img)
+            #     # img = self.color_transforms(img)
+            #     img = self.train_transforms(img)
             img = self.val_transforms(img)
             # img = np.asarray(Image.open(tile_path)).astype(np.uint8)
             # img = np.moveaxis(img, 2, 0)
@@ -252,6 +254,7 @@ class JPGMILDataloader(data_utils.Dataset):
         patient = self.slide_patient_dict[wsi_name]
         # except KeyError:
         #     print(f'{wsi_name} is not included in label file {self.slide_patient_dict_path}')
+        
 
         return wsi_batch, (wsi_name, coords_batch, patient)
     
@@ -259,7 +262,7 @@ class JPGMILDataloader(data_utils.Dataset):
         return [self.labels[i] for i in indices]
 
 
-    def to_fixed_size_bag(self, bag, names, bag_size: int = 512):
+    def to_fixed_size_bag(self, bag, names, bag_size: int = 250):
 
         #duplicate bag instances unitl 
 
@@ -294,7 +297,7 @@ class JPGMILDataloader(data_utils.Dataset):
         # else:
         t = self.files[index]
         # label = self.labels[index]
-        if self.mode=='train':
+        if self.mode=='train' or self.mode=='val':
 
             batch, (wsi_name, batch_coords, patient) = self.get_data(t)
             label = self.labels[index]
@@ -329,7 +332,6 @@ class JPGMILDataloader(data_utils.Dataset):
             # out_batch = torch.stack(out_batch)
             
             # ft = ft.view(-1, 512)
-            
         else:
             batch, (wsi_name, batch_coords, patient) = self.get_data(t)
             label = self.labels[index]
diff --git a/code/datasets/local_feature_dataloader.py b/code/datasets/local_feature_dataloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a45605302ac95b9742455d3cf9c0d5c023c5c791
--- /dev/null
+++ b/code/datasets/local_feature_dataloader.py
@@ -0,0 +1,493 @@
+import pandas as pd
+
+import numpy as np
+import torch
+from torch import Tensor
+from torch.autograd import Variable
+from torch.nn.functional import one_hot
+import torch.nn.functional as F
+from torch.utils import data
+from torch.utils.data import random_split, DataLoader
+from torchsampler import ImbalancedDatasetSampler
+from torchvision import datasets, transforms
+import pandas as pd
+from sklearn.utils import shuffle
+from pathlib import Path
+from tqdm import tqdm
+import zarr
+import json
+import cv2
+from PIL import Image
+import h5py
+import math
+
+# from models import TransMIL
+
+
+
+class LocalFeatureBagLoader(data.Dataset):
+    def __init__(self, file_path, label_path, mode, n_classes, model='None',cache=False, mixup=False, aug=False, mix_res=False, data_cache_size=5000, max_size=50, max_bag_size=0,device='cuda'):
+        super().__init__()
+
+        self.data_info = []
+        self.data_cache = {}
+        self.slideLabelDict = {}
+        self.files = []
+        self.labels = []
+        self.data_cache_size = data_cache_size
+        self.mode = mode
+        self.file_path = file_path
+        # self.csv_path = csv_path
+        self.label_path = label_path
+        self.n_classes = n_classes
+        # self.max_bag_size = max_bag_size
+        self.drop_rate = 0.2
+        # self.min_bag_size = 120
+        self.empty_slides = []
+        self.corrupt_slides = []
+        self.cache = cache
+        # self.mixup = mixup
+        self.aug = False
+        # self.file_path_mix = self.file_path.replace('256', '1024')
+        self.missing = []
+        self.use_1024 = False
+        self.max_size = max_size
+        self.device = device
+        self.dist = []
+
+
+        # print('Using FeatureBagLoader: ', self.mode)
+
+        home = Path.cwd().parts[1]
+        
+        self.slide_patient_dict_path = f'/{home}/ylan/data/DeepGraft/training_tables/slide_patient_dict_an_ext.json'
+        with open(self.slide_patient_dict_path, 'r') as f:
+            self.slide_patient_dict = json.load(f)
+
+        # read labels and slide_path from csv
+
+        with open(self.label_path, 'r') as f:
+            json_dict = json.load(f)
+            if self.mode == 'fine_tune':
+                temp_slide_label_dict = json_dict['train'] + json_dict['test_mixin']
+            else: temp_slide_label_dict = json_dict[self.mode]
+            # temp_slide_label_dict = json_dict['train']
+            # temp_slide_label_dict = json_dict['train'] + json_dict['test_mixin'] # simulate fine tuning
+            
+            for (x,y) in temp_slide_label_dict:
+                
+                # test_path = Path(self.file_path)
+                # if Path(self.file_path) / 
+                # if self.mode != 'test':
+                    # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_RETCCL_2048_HED')
+                    # x = x.replace('FEATURES_RETCCL_2048', 'TEST')
+                # else:
+                    # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_RESNET50_1024_HED')
+                    
+                # x = x.replace('FEATURES_RETCCL_2048', 'FEATURES_CTRANSPATH_768')
+                # else:
+                    # x = x.replace('Aachen_Biopsy_Slides', 'Aachen_Biopsy_Slides_extended')
+                x_name = Path(x).stem
+                # print(x)
+                # print(x_name)
+                if x_name in self.slide_patient_dict.keys():
+                    x_path_list = [Path(self.file_path)/x]
+                    # x_name = x.stem
+                    # x_path_list = [Path(self.file_path)/ x for (x,y) in temp_slide_label_dict]
+                    # print(x)
+                    if self.aug:
+                        for i in range(10):
+                            aug_path = Path(self.file_path)/f'{x}_aug{i}'
+                            if self.use_1024:
+                                aug_path = Path(f'{aug_path}-1024')
+                            if aug_path.exists():
+                                # aug_path = Path(self.file_path)/f'{x}_aug{i}'
+                                x_path_list.append(aug_path)
+                    else: 
+                        aug_path = Path(self.file_path)/f'{x}_aug0'
+                        if self.use_1024:
+                            aug_path = Path(f'{aug_path}-1024')
+                        if aug_path.exists():
+                            x_path_list.append(aug_path)
+                    # print('x_path_list: ', len(x_path_list))
+                    for x_path in x_path_list: 
+                        # print(x_path)
+                        # print(x_path)
+                        # x_path = Path(f'{x_path}.pt')
+                        if x_path.exists():
+                            label = int(y)
+                            wsi_name = x_name
+                            patient = self.slide_patient_dict[wsi_name]
+                            idx = -1
+                            # self.slideLabelDict[x_name] = y
+                            self.labels.append(int(y))
+                            # self.files.append(x_path)
+                            self.data_info.append({'data_path': x_path, 'label': label, 'name': wsi_name, 'patient': patient,'cache_idx': idx})
+                        # elif Path(str(x_path) + '.zarr').exists():
+                        #     self.slideLabelDict[x] = y
+                        #     self.files.append(str(x_path)+'.zarr')
+                        # else:
+                        #     self.missing.append(x)
+
+        self.feature_bags = []
+        
+        self.wsi_names = []
+        self.coords = []
+        self.patients = []
+        # if self.cache:
+        #     for t in tqdm(self.files):
+        #         # zarr_t = str(t) + '.zarr'
+        #         batch, (wsi_name, batch_coords, patient) = self.get_data(t)
+        #         batch = batch.to(self.device)
+        #         # print(label)
+        #         # self.labels.append(label)
+        #         self.feature_bags.append(batch)
+        #         self.wsi_names.append(wsi_name)
+        #         self.coords.append(batch_coords)
+        #         self.patients.append(patient)
+        # else: 
+        #     for t in tqdm(self.files):
+        #         self.labels = 
+
+    def get_data(self, i):
+
+        fp = self.data_info[i]['data_path']
+        if fp not in self.data_cache:
+            self._load_data(fp)
+
+        cache_idx = self.data_info[i]['cache_idx']
+        label = self.data_info[i]['label']
+        wsi_name = self.data_info[i]['name']
+        patient = self.data_info[i]['patient']
+
+        return self.data_cache[fp][cache_idx], label, wsi_name, patient
+    
+    def get_dist(self):
+        return self.dist
+    
+    def get_labels(self):
+        return self.labels
+
+    def __len__(self):
+        return len(self.data_info)
+
+    def __getitem__(self, index):
+
+        # if self.cache:
+        #     label = self.labels[index]
+        #     bag = self.feature_bags[index]
+            
+        #     wsi_name = self.wsi_names[index]
+        #     batch_coords = self.coords[index]
+        #     patient = self.patients[index]
+        # else:
+        # t = self.files[index]
+        # label = self.labels[index]
+        (bag, torch_coords), label, wsi_name, patient = self.get_data(index)
+
+        # if self.mode == 'train' or self.mode == 'fine_tune':
+        # bag_size = bag.shape[0]
+
+        out_bag = torch.permute(bag, (2,0,1))
+
+        if self.mode == 'train':
+            return out_bag, label, (wsi_name, patient)
+        elif self.mode == 'val':
+            return out_bag, label, (wsi_name, torch_coords, patient)
+        else:
+            return out_bag, label, (wsi_name, torch_coords, patient)
+        # return out_bag, label, (wsi_name, batch_coords, patient)
+
+    # def _add_data_infos(self, file_path, cache, slide_patient_dict):
+
+    #     wsi_name = Path(file_path).stem
+    #     if wsi_name in self.slideLabelDict:
+    #         # if wsi_name[:2] != 'RU': #skip RU because of container problems in dataset
+    #         label = self.slideLabelDict[wsi_name]
+    #         patient = slide_patient_dict[wsi_name]
+    #         idx = -1
+    #         self.data_info.append({'data_path': file_path, 'label': label, 'name': wsi_name, 'patient': patient,'cache_idx': idx})
+
+    def _load_data(self, file_path):
+        """Load data to the cache given the file
+        path and update the cache index in the
+        data_info structure.
+        """
+        batch_names=[] #add function for name_batch read out
+
+        # wsi_name = Path(file_path).stem
+        # base_file = file_path.with_suffix('')
+        # if wsi_name.split('_')[-1][:3] == 'aug':
+        # parts = wsi_name.rsplit('_', 1)
+        # if parts[1][:3] == 'aug':
+        #     if parts[1].split('-')[0] == '1024':
+        #         wsi_name = parts[0]
+        #     else: 
+        #         wsi_name = '_'.join(parts[:-1])
+        # patient = self.slide_patient_dict[wsi_name]
+        # print(file_path)
+        with h5py.File(file_path, 'r') as hdf5_file:
+            np_bag = hdf5_file['features'][:]
+            coords = hdf5_file['coords'][:]
+
+        # Order by coordinates!
+        torch_bag = torch.from_numpy(np_bag)
+        torch_coords = torch.from_numpy(coords)
+        #get max coords for assembly    
+        x_max = torch.max(torch_coords[:,0])
+        y_max = torch.max(torch_coords[:,1])
+        x_min = torch.min(torch_coords[:,0])
+        y_min = torch.min(torch_coords[:,1])
+
+        self.dist.append((x_max-x_min, y_max-y_min))
+
+        # print(x_min, x_max)
+        if x_max-x_min > self.max_size:
+            x_start_pos = torch.randint(x_min, x_max-self.max_size, [1])
+            x_end_pos = x_start_pos + self.max_size
+        else: 
+            x_start_pos = x_min 
+            x_end_pos = x_max
+
+        if y_max-y_min > self.max_size:
+            y_start_pos = torch.randint(y_min, y_max-self.max_size, [1])
+            y_end_pos = y_start_pos + self.max_size
+        else: 
+            y_start_pos = y_min
+            y_end_pos = y_max
+
+        slide_3d = torch.zeros([self.max_size, self.max_size, 2048]) #feature vector size
+
+
+        # Define a size for a 3D feature stack! 
+        for c, patch_features in zip(torch_coords, torch_bag):
+            x = c[0]
+            y = c[1]
+            if x > x_start_pos and x < x_end_pos and y > y_start_pos and y < y_end_pos:
+
+                # print(x,x_start_pos, x_end_pos)
+                # print(y,y_start_pos, y_end_pos)
+
+                slide_3d[x-x_start_pos, y-y_start_pos, :] = patch_features
+
+        # slide_3d = slide_3d[500, 500, :]
+        # limit size of slide to self.max_size
+
+
+        # if slide_3d.shape[0] > self.max_size:
+        #     slide_3d = slide_3d[self.max_size, :, :]
+        # if slide_3d.shape[1] > self.max_size:
+        #     slide_3d = slide_3d[:, self.max_size, :]
+
+        # padding_x1 = math.floor((self.max_size - slide_3d.shape[0])/2)
+        # padding_x2 = self.max_size - padding_x1 - slide_3d.shape[0]
+        # padding_y1 = math.floor((self.max_size - slide_3d.shape[1])/2)
+        # padding_y2 = self.max_size - padding_y1 - slide_3d.shape[1]
+        
+        # padding = (0, 0, padding_y1, padding_y2, padding_x1, padding_x2)
+        # wsi_bag = F.pad(slide_3d, padding, mode='constant') #pad to max_size 
+        # print(slide_3d.shape)
+        wsi_bag = slide_3d
+        # return wsi_bag, (wsi_name, batch_coords, patient)
+
+        # add data to cache, get id for cache entry
+        idx = self._add_to_cache((wsi_bag, torch_coords), file_path)
+        file_idx = next(i for i,v in enumerate(self.data_info) if v['data_path'] == file_path)
+        self.data_info[file_idx + idx]['cache_idx'] = idx
+
+        # remove an element from data cache if size was exceeded
+        if len(self.data_cache) > self.data_cache_size:
+            # remove one item from the cache at random
+            removal_keys = list(self.data_cache)
+            removal_keys.remove(file_path)
+            self.data_cache.pop(removal_keys[0])
+            # remove invalid cache_idx
+            # self.data_info = [{'data_path': di['data_path'], 'label': di['label'], 'shape': di['shape'], 'name': di['name'], 'cache_idx': -1} if di['data_path'] == removal_keys[0] else di for di in self.data_info]
+            self.data_info = [{'data_path': di['data_path'], 'label': di['label'], 'name': di['name'], 'patient':di['patient'], 'cache_idx': -1} if di['data_path'] == removal_keys[0] else di for di in self.data_info]
+
+    def _add_to_cache(self, data, data_path):
+        """Adds data to the cache and returns its index. There is one cache
+        list for every file_path, containing all datasets in that file.
+        """
+        if data_path not in self.data_cache:
+            self.data_cache[data_path] = [data]
+        else:
+            self.data_cache[data_path].append(data)
+        return len(self.data_cache[data_path]) - 1
+
+    def get_name(self, i):
+        # name = self.get_data_infos(type)[i]['name']
+        name = self.data_info[i]['name']
+        return name
+
+    # def get_labels(self, indices):
+
+    #     return [self.data_info[i]['label'] for i in indices]
+        # return self.slideLabelDict.values()
+
+
+    def to_fixed_size_bag(self, bag, names, bag_size: int = 512):
+
+        bag_idxs = torch.randperm(bag.shape[0])[:bag_size]
+        bag_samples = bag[bag_idxs]
+        name_samples = [names[i] for i in bag_idxs]
+
+        return bag_samples, name_samples, min(bag_size, len(bag))
+
+    def data_dropout(self, bag, batch_names, drop_rate):
+        # bag_size = self.max_bag_size
+        bag_size = bag.shape[0]
+        bag_idxs = torch.randperm(self.max_bag_size)[:int(bag_size*(1-drop_rate))]
+        bag_samples = bag[bag_idxs]
+        name_samples = [batch_names[i] for i in bag_idxs]
+
+        return bag_samples, name_samples
+
+    def get_mixup_bag(self, bag):
+
+        bag_size = bag.shape[0]
+
+        a = torch.rand([bag_size])
+        b = 0.6
+        rand_x = torch.randint(0, bag_size, [bag_size,])
+        rand_y = torch.randint(0, bag_size, [bag_size,])
+
+        bag_x = bag[rand_x, :]
+        bag_y = bag[rand_y, :]
+
+
+        temp_bag = (bag_x.t()*a).t() + (bag_y.t()*(1.0-a)).t()
+
+        if bag_size < self.max_bag_size:
+            diff = self.max_bag_size - bag_size
+            bag_idxs = torch.randperm(bag_size)[:diff]
+            
+            mixup_bag = torch.cat((bag, temp_bag[bag_idxs, :]))
+        else:
+            random_sample_list = torch.rand(bag_size)
+            mixup_bag = [bag[i] if random_sample_list[i] else temp_bag[i] > b for i in range(bag_size)] #make pytorch native?!
+            mixup_bag = torch.stack(mixup_bag)
+
+        return mixup_bag
+
+if __name__ == '__main__':
+    
+#%%
+    from pathlib import Path
+    import os
+    import time
+    # from fast_tensor_dl import FastTensorDataLoader
+    from custom_resnet50 import resnet50_baseline
+    from torchvision import models
+    import matplotlib.pyplot as plt
+    
+    home = Path.cwd().parts[1]
+    train_csv = f'/{home}/ylan/DeepGraft_project/code/debug_train.csv'
+    data_root = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated'
+    # data_root = f'/{home}/ylan/DeepGraft/dataset/hdf5/256_256um_split/'
+    # label_path = f'/{home}/ylan/DeepGraft_project/code/split_PAS_bin.json'
+    # label_path = f'/{home}/ylan/DeepGraft/training_tables/split_debug.json'
+    label_path = f'/{home}/ylan/data/DeepGraft/training_tables/dg_split_PAS_HE_Jones_Grocott_norm_rest_ext.json'
+    # label_path = f'/{home}/ylan/data/DeepGraft/training_tables/dg_limit_20_split_PAS_HE_Jones_norm_rest.json'
+    # output_dir = f'/{data_root}/debug/augments'
+    # os.makedirs(output_dir, exist_ok=True)
+    n_classes = 2
+
+    train_dataset = LocalFeatureBagLoader(data_root, label_path=label_path, mode='train', cache=False, n_classes=n_classes)
+    print('train_dataset: ', len(train_dataset))
+
+    def simple_collate(data):
+        # print(data[0])
+        bags = [i[0] for i in data]
+        labels = [i[1] for i in data]
+        name = [i[2][0] for i in data]
+        patient = [i[2][1] for i in data]
+        bags = torch.stack(bags)
+        labels = torch.Tensor(np.stack(labels, axis=0)).long()
+        return bags, labels, (name, patient)
+
+
+    train_dl = DataLoader(train_dataset, batch_size=5, sampler=ImbalancedDatasetSampler(train_dataset), collate_fn=simple_collate) #
+
+    print('train_dl: ', len(train_dl))
+
+    # train_dataset = FeatureBagLoader(data_root, label_path=label_path, mode='train', cache=False, n_classes=n_classes, model='None', aug=True, mixup=True)
+    # test_dataset = FeatureBagLoader(data_root, label_path=label_path, mode='test', cache=False, n_classes=n_classes, model='None', aug=True, mixup=True)
+    # test_dl = DataLoader(test_dataset, batch_size=1)
+    # print('test_dl: ', len(test_dl))
+
+    # # print(dataset.get_labels(0))
+    # # a = int(len(dataset)* 0.8)
+    # # b = int(len(dataset) - a)
+    # # train_data, valid_data = random_split(dataset, [a, b])
+
+    # val_dataset = FeatureBagLoader(data_root, label_path=label_path, mode='val', cache=False, mixup=False, aug=False, n_classes=n_classes, model='None')
+    # valid_dl = DataLoader(val_dataset, batch_size=1)
+    # print('valid_dl: ', len(valid_dl))
+
+    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+    # scaler = torch.cuda.amp.GradScaler()
+
+    # model_ft = resnet50_baseline()
+    # model = models.resnet50(weights='IMAGENET1K_V1')
+    # model.conv1 = torch.nn.Sequential(
+    #     torch.nn.Conv2d(2048, 1024, kernel_size=(7,7), stride=(2,2)),
+    #     torch.nn.BatchNorm2d(1024),
+    #     torch.nn.ReLU,
+    #     torch.nn.MaxPool2d(kernel_size=3)
+    # )
+    # model.conv1 = torch.nn.Conv2d(2048, 64, kernel_size=(7,7), stride=(2,2), padding=(3,3))
+    # # print(model)
+    # model.fc = torch.nn.Sequential(
+    #     torch.nn.Linear(model.fc.in_features, n_classes),
+    # )
+
+    # for param in model_ft.parameters():
+    #     param.requires_grad = False
+    # print(list(model_ft.children()))
+    # model_ft.fc = model_ft.
+    # model_ft.to(device)
+    
+
+    # model = TransMIL(n_classes=n_classes).to(device)
+    
+
+    # print(dataset.get_labels(np.arange(len(dataset))))
+
+    c = 0
+    # label_count = [0] *n_classes
+    epochs = 1
+    # # print(len(dl))
+    # # start = time.time()
+    print(device)
+    for i in range(epochs):
+        start = time.time()
+        for item in tqdm(train_dl): 
+            # print(item)
+            bag, label, (name, patient) = item
+            print(bag.shape)
+            bag.to(device)
+            # pred = model(bag)
+            c += 1
+        end = time.time()
+        print('Bag Time: ', end-start)
+
+
+    # dist_array = train_dataset.get_dist()
+    # x_dist = [x[0] for x in dist_array]
+    # y_dist = [x[1] for x in dist_array]
+
+    # h_x = np.histogram(x_dist)
+    # h_y = np.histogram(y_dist)
+
+    # print(h_x)
+    # print(h_y)
+
+
+    # _ = plt.hist(h_x, bins='auto')
+    # plt.show()
+
+    # _ = plt.hist(h_y, bins='auto')
+    # plt.show()
+
+    
diff --git a/code/models/CTMIL.py b/code/models/CTMIL.py
new file mode 100644
index 0000000000000000000000000000000000000000..e455134af3ff33aa797dae6795ca32763195f6f2
--- /dev/null
+++ b/code/models/CTMIL.py
@@ -0,0 +1,195 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import numpy as np
+from nystrom_attention import NystromAttention
+from collections import OrderedDict
+from ._transformer import PreNorm, Attention, FeedForward
+from einops import repeat
+
+try:
+    import apex
+    apex_available=True
+except ModuleNotFoundError:
+    # Error handling
+    apex_available = False
+    pass
+
+class Transformer(nn.Module):
+    def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout = 0.):
+        super().__init__()
+        self.layers = nn.ModuleList([])
+        for _ in range(depth):
+            self.layers.append(nn.ModuleList([
+                PreNorm(dim, Attention(dim, heads = heads, dim_head = dim_head, dropout = dropout)),
+                PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout))
+            ]))
+
+    def forward(self, x): #, register_hook=False
+        for attn, ff in self.layers:
+            x = attn(x) + x # , register_hook=register_hook
+            x = ff(x) + x
+        return x
+
+class TransLayer(nn.Module):
+
+    def __init__(self, norm_layer=nn.LayerNorm, dim=512):
+        super().__init__()
+        self.norm = norm_layer(dim)
+        self.attn = NystromAttention(
+            dim = dim,
+            dim_head = dim//8,
+            heads = 8,
+            num_landmarks = dim//2,    # number of landmarks
+            pinv_iterations = 6,    # number of moore-penrose iterations for approximating pinverse. 6 was recommended by the paper
+            residual = True,         # whether to do an extra residual with the value or not. supposedly faster convergence if turned on
+            dropout=0.7 #0.1
+        )
+
+    def forward(self, x):
+        out, attn = self.attn(self.norm(x), return_attn=True)
+        x = x + out
+        # x = x + self.attn(self.norm(x))
+
+        return x, attn
+
+
+class PPEG(nn.Module):
+    def __init__(self, dim=512):
+        super(PPEG, self).__init__()
+        self.proj = nn.Conv2d(dim, dim, 7, 1, 7//2, groups=dim)
+        self.proj1 = nn.Conv2d(dim, dim, 5, 1, 5//2, groups=dim)
+        self.proj2 = nn.Conv2d(dim, dim, 3, 1, 3//2, groups=dim)
+
+    def forward(self, x, H, W):
+        B, _, C = x.shape
+        cls_token, feat_token = x[:, 0], x[:, 1:]
+        cnn_feat = feat_token.transpose(1, 2).view(B, C, H, W)
+        x = self.proj(cnn_feat)+cnn_feat+self.proj1(cnn_feat)+self.proj2(cnn_feat)
+        x = x.flatten(2).transpose(1, 2)
+        x = torch.cat((cls_token.unsqueeze(1), x), dim=1)
+        return x
+
+
+class CTMIL(nn.Module):
+    def __init__(self, n_classes, in_features, out_features=512):
+        super(CTMIL, self).__init__()
+        # print('CTMIL!')
+        # in_features = 2048
+        # out_features = 512
+        
+
+        self.pos_layer_0 = PPEG(dim=out_features)
+        if apex_available: 
+            norm_layer = apex.normalization.FusedLayerNorm
+        else:
+            norm_layer = nn.LayerNorm
+
+        self.conv1 = nn.Sequential(
+            nn.Conv2d(in_features, int(in_features/2), kernel_size=3, stride=1, padding=1, bias=False),
+            nn.BatchNorm2d(int(in_features/2)),
+            nn.GELU(),
+            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+        )
+        self.conv2 = nn.Sequential(
+            nn.Conv2d(int(in_features/2), out_features, kernel_size=3, stride=1, padding=1, bias=False),
+            nn.BatchNorm2d(out_features),
+            nn.GELU(),
+            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+        )
+
+        if in_features == 2048:
+            self._fc1 = nn.Sequential(
+                nn.Linear(in_features, int(in_features/2), bias=True),
+                nn.Linear(int(in_features/2), out_features), 
+                nn.GELU(),
+                )
+        elif in_features == 1024:
+            self._fc1 = nn.Sequential(
+                # nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.2), norm_layer(out_features),
+                nn.Linear(in_features, out_features), nn.GELU(), nn.Dropout(p=0.6), norm_layer(out_features)
+                ) 
+        elif in_features == 768:
+            self._fc1 = nn.Sequential(nn.Linear(in_features, 512, bias=True), nn.ReLU())
+        # self._fc1 = nn.Sequential(nn.Linear(1024, 512), nn.ReLU())
+        self.cls_token = nn.Parameter(torch.randn(1, 1, out_features))
+
+
+        
+
+        self.n_classes = n_classes
+        self.layer1 = TransLayer(dim=out_features)
+        self.layer2 = TransLayer(dim=out_features)
+        self.norm = nn.LayerNorm(out_features)
+        self._fc2 = nn.Linear(out_features, self.n_classes)
+
+        # dropout=0.5
+        # emb_dropout=0.5
+        # pool='cls'
+        # self.transformer1 = Transformer(dim=out_features, depth=2, dim_head=64, heads=8, mlp_dim=512, dropout=dropout)
+        # self.transformer2 = Transformer(dim=out_features, depth=2, dim_head=64, heads=8, mlp_dim=512, dropout=dropout)
+        # self.dropout = nn.Dropout(emb_dropout)
+        # self.to_latent = nn.Identity()
+        # self.pool = pool
+
+
+
+    def forward(self, x): #, **kwargs
+
+        x = x.squeeze(0)
+        x = self.conv1(x)
+        h = self.conv2(x)
+
+        h = h.view(h.shape[0], h.shape[2]*h.shape[2], h.shape[1]) #shape convolution output to list of vectors
+        H = h.shape[1]
+        _H, _W = int(np.ceil(np.sqrt(H))), int(np.ceil(np.sqrt(H)))
+        add_length = _H * _W - H
+        h = torch.cat([h, h[:,:add_length,:]],dim = 1) #[B, N, 512]
+        
+        # #---->cls_token
+        B = h.shape[0] #batch_size
+        cls_tokens = self.cls_token.expand(B, -1, -1).cuda()
+        h = torch.cat((cls_tokens, h), dim=1)
+
+
+        # #---->Translayer x1
+        h, _ = self.layer1(h) #[B, N, 512]
+        h = self.pos_layer_0(h, _H, _W) #[B, N, 512]
+        h, _ = self.layer2(h) #[B, N, 512]
+        
+        h = self.norm(h)[:,0]
+
+        #---->predict
+        logits = self._fc2(h) #[B, n_classes]
+        # return logits, attn2
+        return logits
+
+if __name__ == "__main__":
+    data = torch.randn((1, 5, 2048, 50, 50)).cuda()
+    model = TransformerMIL(n_classes=2, in_features=2048, out_features=512).cuda()
+    model.eval()
+    # print(model.eval())
+    # logits, attn = model(data)
+    # cls_attention = attn[:,:, 0, :6000]
+    # values, indices = torch.max(cls_attention, 1)
+    # mean = values.mean()
+    # zeros = torch.zeros(values.shape).cuda()
+    # filtered = torch.where(values > mean, values, zeros)
+    
+    # filter = values > values.mean()
+    # filtered_values = values[filter]
+    # values = np.where(values>values.mean(), values, 0)
+    output = model(data)
+    print(output.shape)
+
+    # print(filtered.shape)
+
+
+    # values = [v if v > values.mean().item() else 0 for v in values]
+    # print(values)
+    # print(len(values))
+
+    # logits = results_dict['logits']
+    # Y_prob = results_dict['Y_prob']
+    # Y_hat = results_dict['Y_hat']
+    # print(F.sigmoid(logits))
diff --git a/code/models/SimCLR.py b/code/models/SimCLR.py
new file mode 100644
index 0000000000000000000000000000000000000000..f98844884d30cf97e445251419462a2870b55b88
--- /dev/null
+++ b/code/models/SimCLR.py
@@ -0,0 +1,61 @@
+class SimCLR(L.LightningModule):
+    def __init__(self, hidden_dim, lr, temperature, weight_decay, max_epochs=500):
+        super().__init__()
+        self.save_hyperparameters()
+        assert self.hparams.temperature > 0.0, "The temperature must be a positive float!"
+        # Base model f(.)
+        self.convnet = torchvision.models.resnet18(
+            pretrained=False, num_classes=4 * hidden_dim
+        )  # num_classes is the output size of the last linear layer
+        # The MLP for g(.) consists of Linear->ReLU->Linear
+        self.convnet.fc = nn.Sequential(
+            self.convnet.fc,  # Linear(ResNet output, 4*hidden_dim)
+            nn.ReLU(inplace=True),
+            nn.Linear(4 * hidden_dim, hidden_dim),
+        )
+
+    def configure_optimizers(self):
+        optimizer = optim.AdamW(self.parameters(), lr=self.hparams.lr, weight_decay=self.hparams.weight_decay)
+        lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(
+            optimizer, T_max=self.hparams.max_epochs, eta_min=self.hparams.lr / 50
+        )
+        return [optimizer], [lr_scheduler]
+
+    def info_nce_loss(self, batch, mode="train"):
+        imgs, _ = batch
+        imgs = torch.cat(imgs, dim=0)
+
+        # Encode all images
+        feats = self.convnet(imgs)
+        # Calculate cosine similarity
+        cos_sim = F.cosine_similarity(feats[:, None, :], feats[None, :, :], dim=-1)
+        # Mask out cosine similarity to itself
+        self_mask = torch.eye(cos_sim.shape[0], dtype=torch.bool, device=cos_sim.device)
+        cos_sim.masked_fill_(self_mask, -9e15)
+        # Find positive example -> batch_size//2 away from the original example
+        pos_mask = self_mask.roll(shifts=cos_sim.shape[0] // 2, dims=0)
+        # InfoNCE loss
+        cos_sim = cos_sim / self.hparams.temperature
+        nll = -cos_sim[pos_mask] + torch.logsumexp(cos_sim, dim=-1)
+        nll = nll.mean()
+
+        # Logging loss
+        self.log(mode + "_loss", nll)
+        # Get ranking position of positive example
+        comb_sim = torch.cat(
+            [cos_sim[pos_mask][:, None], cos_sim.masked_fill(pos_mask, -9e15)],  # First position positive example
+            dim=-1,
+        )
+        sim_argsort = comb_sim.argsort(dim=-1, descending=True).argmin(dim=-1)
+        # Logging ranking metrics
+        self.log(mode + "_acc_top1", (sim_argsort == 0).float().mean())
+        self.log(mode + "_acc_top5", (sim_argsort < 5).float().mean())
+        self.log(mode + "_acc_mean_pos", 1 + sim_argsort.float().mean())
+
+        return nll
+
+    def training_step(self, batch, batch_idx):
+        return self.info_nce_loss(batch, mode="train")
+
+    def validation_step(self, batch, batch_idx):
+        self.info_nce_loss(batch, mode="val")
diff --git a/code/models/TransMIL.py b/code/models/TransMIL.py
index 87625d9af97757af57261ebea1c0cdd5ab69246f..a8c7cccdc9a38d42353dddb914209b014a99e31b 100755
--- a/code/models/TransMIL.py
+++ b/code/models/TransMIL.py
@@ -3,8 +3,8 @@ import torch.nn as nn
 import torch.nn.functional as F
 import numpy as np
 from nystrom_attention import NystromAttention
-import models.ResNet as ResNet
-from pathlib import Path
+# import models.ResNet as ResNet
+# from pathlib import Path
 
 try:
     import apex
@@ -21,10 +21,12 @@ class TransLayer(nn.Module):
     def __init__(self, norm_layer=nn.LayerNorm, dim=512):
         super().__init__()
         self.norm = norm_layer(dim)
+
+        attention_heads = 8 #8
         self.attn = NystromAttention(
             dim = dim,
-            dim_head = dim//8,
-            heads = 8,
+            dim_head = dim//attention_heads, #dim//8
+            heads = attention_heads,
             num_landmarks = dim//2,    # number of landmarks
             pinv_iterations = 6,    # number of moore-penrose iterations for approximating pinverse. 6 was recommended by the paper
             residual = True,         # whether to do an extra residual with the value or not. supposedly faster convergence if turned on
@@ -81,12 +83,21 @@ class TransMIL(nn.Module):
 
         if in_features == 2048:
             self._fc1 = nn.Sequential(
+                # nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/2)),
+                # nn.Linear(int(in_features/2), int(in_features/4)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/4)),
+                # nn.Linear(int(in_features/4), out_features), nn.GELU(),
                 nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/2)),
+                # nn.Linear(int(in_features/2), int(in_features/4)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/4)),
                 nn.Linear(int(in_features/2), out_features), nn.GELU(),
                 ) 
+            
+            # self._fc1 = nn.Sequential(
+            #     nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/2)),
+            #     nn.Linear(int(in_features/2), out_features), nn.GELU(),
+            #     ) 
         elif in_features == 1024:
             self._fc1 = nn.Sequential(
-                # nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.2), norm_layer(out_features),
+                nn.Linear(in_features, int(in_features)), nn.GELU(), nn.Dropout(p=0.2), norm_layer(out_features),
                 nn.Linear(in_features, out_features), nn.GELU(), nn.Dropout(p=0.6), norm_layer(out_features)
                 ) 
         elif in_features == 768:
@@ -94,6 +105,11 @@ class TransMIL(nn.Module):
                 nn.Linear(in_features, int(in_features)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(in_features),
                 nn.Linear(in_features, out_features), nn.GELU(), nn.Dropout(p=0.6), norm_layer(out_features)
                 ) 
+        elif in_features == 384:
+            self._fc1 = nn.Sequential(
+                # nn.Linear(in_features, int(in_features)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(in_features),
+                nn.Linear(in_features, out_features), nn.GELU(),
+                ) 
         # out_features = 256 
         # self._fc1 = nn.Sequential(
         #     nn.Linear(in_features, out_features), nn.GELU(), nn.Dropout(p=0.2), norm_layer(out_features)
@@ -122,11 +138,11 @@ class TransMIL(nn.Module):
         # self.model_ft.fc = nn.Linear(2048, self.in_features)
 
 
-    def forward(self, x): #, **kwargs
+    def forward(self, x, return_attn=False): #, **kwargs
 
         # x = self.model_ft(x).unsqueeze(0)
         # print(x.shape)
-        # x = x.unsqueeze(0) # needed for feature extractorVisualization!!!
+        # x = x.unsqueeze(0) # needed for feature extractor Visualization!!!
         # print(x.shape)
         if x.dim() > 3:
             x = x.squeeze(0)
@@ -137,7 +153,9 @@ class TransMIL(nn.Module):
         # print('Feature Representation: ', h.shape)
         #---->duplicate pad
         H = h.shape[1]
+        # print(h.size[1])    
         _H, _W = int(np.ceil(np.sqrt(H))), int(np.ceil(np.sqrt(H)))
+        # _H, _W =   H.sqrt().ceil().int(), H.sqrt().ceil().int(),
         add_length = _H * _W - H
 
         # print(h.shape)
@@ -166,7 +184,7 @@ class TransMIL(nn.Module):
 
         # print('After second TransLayer: ', h.shape) #[1, 1025, 512] 1025 = cls_token + 1024
         #---->cls_token
-        
+        # hh = self.norm(h)
         h = self.norm(h)[:,0]
         # print(h.shape)
 
@@ -175,15 +193,17 @@ class TransMIL(nn.Module):
         # Y_hat = torch.argmax(logits, dim=1)
         # Y_prob = F.softmax(logits, dim = 1)
         # results_dict = {'logits': logits, 'Y_prob': Y_prob, 'Y_hat': Y_hat}
-        return logits
-        # return logits, attn2
+        # return logits
+        if return_attn:
+            return logits, attn2
+        else: return logits
 
 if __name__ == "__main__":
     
     data = torch.randn((1, 6000, 1024)).cuda()
-    model = TransMIL(n_classes=2).cuda()
-    print(model.eval())
-    logits, attn = model(data)
+    model = TransMIL(in_features=1024, n_classes=2).cuda()
+    # print(model.eval())
+    logits, attn = model(data, return_attn=True)
     cls_attention = attn[:,:, 0, :6000]
     values, indices = torch.max(cls_attention, 1)
     mean = values.mean()
@@ -195,6 +215,7 @@ if __name__ == "__main__":
     # values = np.where(values>values.mean(), values, 0)
 
     print(filtered.shape)
+    print(filtered)
 
 
     # values = [v if v > values.mean().item() else 0 for v in values]
diff --git a/code/models/TransformerMIL.py b/code/models/TransformerMIL.py
index 467a7548634748aa07fe584b7c5c357d7c6e666d..a0f3b002769eb7e62e70fd0357818f0841f9eabc 100644
--- a/code/models/TransformerMIL.py
+++ b/code/models/TransformerMIL.py
@@ -3,6 +3,9 @@ import torch.nn as nn
 import torch.nn.functional as F
 import numpy as np
 from nystrom_attention import NystromAttention
+from collections import OrderedDict
+from ._transformer import PreNorm, Attention, FeedForward
+from einops import repeat
 
 try:
     import apex
@@ -12,6 +15,22 @@ except ModuleNotFoundError:
     apex_available = False
     pass
 
+class Transformer(nn.Module):
+    def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout = 0.):
+        super().__init__()
+        self.layers = nn.ModuleList([])
+        for _ in range(depth):
+            self.layers.append(nn.ModuleList([
+                PreNorm(dim, Attention(dim, heads = heads, dim_head = dim_head, dropout = dropout)),
+                PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout))
+            ]))
+
+    def forward(self, x): #, register_hook=False
+        for attn, ff in self.layers:
+            x = attn(x) + x # , register_hook=register_hook
+            x = ff(x) + x
+        return x
+
 class TransLayer(nn.Module):
 
     def __init__(self, norm_layer=nn.LayerNorm, dim=512):
@@ -57,119 +76,131 @@ class TransformerMIL(nn.Module):
         super(TransformerMIL, self).__init__()
         # in_features = 2048
         # out_features = 512
+        
+
         self.pos_layer_0 = PPEG(dim=out_features)
-        self.pos_layer_1 = PPEG(dim=out_features)
-        self.pos_layer_2 = PPEG(dim=out_features)
-        self.pos_layer_3 = PPEG(dim=out_features)
-        self.pos_layer_4 = PPEG(dim=out_features)
-        self.pos_layer_5 = PPEG(dim=out_features)
+        # self.pos_layer_1 = PPEG(dim=out_features)
+        # self.pos_layer_2 = PPEG(dim=out_features)
+        # self.pos_layer_3 = PPEG(dim=out_features)
+        # self.pos_layer_4 = PPEG(dim=out_features)
+        # self.pos_layer_5 = PPEG(dim=out_features)
         if apex_available: 
             norm_layer = apex.normalization.FusedLayerNorm
         else:
             norm_layer = nn.LayerNorm
+
+        self.conv1 = nn.Sequential(
+            nn.Conv2d(in_features, int(in_features/2), kernel_size=3, stride=1, padding=1, bias=False),
+            nn.BatchNorm2d(int(in_features/2)),
+            nn.GELU(),
+            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+        )
+        self.conv2 = nn.Sequential(
+            nn.Conv2d(int(in_features/2), out_features, kernel_size=3, stride=1, padding=1, bias=False),
+            nn.BatchNorm2d(out_features),
+            nn.GELU(),
+            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+        )
+
         if in_features == 2048:
-            self._fc1 = nn.Sequential(
+            self.fc1 = nn.Sequential(
                 nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.6), norm_layer(int(in_features/2)),
                 nn.Linear(int(in_features/2), out_features), nn.GELU(),
-                ) 
+                )
         elif in_features == 1024:
-            self._fc1 = nn.Sequential(
+            self.fc1 = nn.Sequential(
                 # nn.Linear(in_features, int(in_features/2)), nn.GELU(), nn.Dropout(p=0.2), norm_layer(out_features),
                 nn.Linear(in_features, out_features), nn.GELU(), nn.Dropout(p=0.6), norm_layer(out_features)
                 ) 
+        elif in_features == 768:
+            self.fc1 = nn.Sequential(nn.Linear(in_features, 512, bias=True), nn.ReLU())
+        elif in_features == 384:
+            self.fc1 = nn.Sequential(nn.Linear(in_features, 512, bias=True), nn.ReLU())
         # self._fc1 = nn.Sequential(nn.Linear(1024, 512), nn.ReLU())
         self.cls_token = nn.Parameter(torch.randn(1, 1, out_features))
+        
         self.n_classes = n_classes
         self.layer1 = TransLayer(dim=out_features)
         self.layer2 = TransLayer(dim=out_features)
-        self.layer3 = TransLayer(dim=out_features)
-        self.layer4 = TransLayer(dim=out_features)
-        self.layer5 = TransLayer(dim=out_features)
-        self.layer6 = TransLayer(dim=out_features)
-        self.layer7 = TransLayer(dim=out_features)
-        self.layer8 = TransLayer(dim=out_features)
-        self.layer9 = TransLayer(dim=out_features)
-        self.layer10 = TransLayer(dim=out_features)
-        self.layer11 = TransLayer(dim=out_features)
-        self.layer12 = TransLayer(dim=out_features)
-        # self.layer4 = TransLayer(dim=out_features)
         self.norm = nn.LayerNorm(out_features)
         self._fc2 = nn.Linear(out_features, self.n_classes)
 
+        dropout=0.5
+        emb_dropout=0.5
+        pool='cls'
+        self.transformer1 = Transformer(dim=out_features, depth=2, dim_head=64, heads=8, mlp_dim=512, dropout=dropout)
+        self.transformer2 = Transformer(dim=out_features, depth=2, dim_head=64, heads=8, mlp_dim=512, dropout=dropout)
+        self.dropout = nn.Dropout(emb_dropout)
+        self.to_latent = nn.Identity()
+        self.pool = pool
 
-    def forward(self, x): #, **kwargs
-
-        h = x.squeeze(0).float() #[B, n, 1024]
-        h = self._fc1(h) #[B, n, 512]
+    def forward(self, x):    
         
-        # print('Feature Representation: ', h.shape)
-        #---->duplicate pad
-        H = h.shape[1]
-        _H, _W = int(np.ceil(np.sqrt(H))), int(np.ceil(np.sqrt(H)))
-        add_length = _H * _W - H
-        h = torch.cat([h, h[:,:add_length,:]],dim = 1) #[B, N, 512]
+        # print(x.shape)
+        x = x.squeeze(0)
+        # print(x.shape)
+        b, n, d = x.shape
+        x = self.fc1(x)
+        cls_tokens = repeat(self.cls_token, '1 1 d -> b 1 d', b=b)
+        x = torch.cat((cls_tokens, x), dim=1)
+        x = self.dropout(x)
+        x = self.transformer1(x)
+        x = self.transformer2(x)
+        x = x.mean(dim=1) if self.pool == 'mean' else x[:, 0]
+        x = self.to_latent(x)
+        x = self.norm(x)
+        return self._fc2(x)
+
+
+    # def forward(self, x): #, **kwargs
+
+    #     x = x.squeeze(0)
+    #     x = self.conv1(x)
+    #     h = self.conv2(x)
+
+    #     h = h.view(h.shape[0], h.shape[2]*h.shape[2], h.shape[1]) #shape convolution output to list of vectors
+    #     H = h.shape[1]
+    #     _H, _W = int(np.ceil(np.sqrt(H))), int(np.ceil(np.sqrt(H)))
+    #     add_length = _H * _W - H
+    #     h = torch.cat([h, h[:,:add_length,:]],dim = 1) #[B, N, 512]
         
+    #     # #---->cls_token
+    #     B = h.shape[0] #batch_size
+    #     cls_tokens = self.cls_token.expand(B, -1, -1).cuda()
+    #     h = torch.cat((cls_tokens, h), dim=1)
 
-        #---->cls_token
-        B = h.shape[0]
-        cls_tokens = self.cls_token.expand(B, -1, -1).cuda()
-        h = torch.cat((cls_tokens, h), dim=1)
-
-
-        #---->Translayer x1
-        h, _ = self.layer1(h) #[B, N, 512]
-        h = self.pos_layer_0(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer2(h) #[B, N, 512]
-        h = self.pos_layer_1(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer4(h) #[B, N, 512]
-        h = self.pos_layer_2(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer5(h) #[B, N, 512]
-        h = self.pos_layer_3(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer6(h) #[B, N, 512]
-        h = self.pos_layer_4(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer7(h) #[B, N, 512]
-        h = self.pos_layer_5(h, _H, _W) #[B, N, 512]
-        h, _ = self.layer8(h) #[B, N, 512]
-        h, _ = self.layer9(h) #[B, N, 512]
-        # h, _ = self.layer10(h) #[B, N, 512]
-        # h, _ = self.layer11(h) #[B, N, 512]
-        # h, _ = self.layer12(h) #[B, N, 512]
-
-        # print('After first TransLayer: ', h.shape)
-
-        #---->PPEG
-        # h = self.pos_layer(h, _H, _W) #[B, N, 512]
-        # # print('After PPEG: ', h.shape)
-        
-        # #---->Translayer x2
-        # h, attn2 = self.layer2(h) #[B, N, 512]
 
-        # print('After second TransLayer: ', h.shape) #[1, 1025, 512] 1025 = cls_token + 1024
-        #---->cls_token
+    #     # #---->Translayer x1
+    #     h, _ = self.layer1(h) #[B, N, 512]
+    #     h = self.pos_layer_0(h, _H, _W) #[B, N, 512]
+    #     h, _ = self.layer2(h) #[B, N, 512]
         
-        h = self.norm(h)[:,0]
+    #     h = self.norm(h)[:,0]
 
-        #---->predict
-        logits = self._fc2(h) #[B, n_classes]
-        # return logits, attn2
-        return logits
+    #     #---->predict
+    #     logits = self._fc2(h) #[B, n_classes]
+    #     # return logits, attn2
+    #     return logits
 
 if __name__ == "__main__":
-    data = torch.randn((1, 6000, 512)).cuda()
-    model = TransMIL(n_classes=2).cuda()
-    print(model.eval())
-    logits, attn = model(data)
-    cls_attention = attn[:,:, 0, :6000]
-    values, indices = torch.max(cls_attention, 1)
-    mean = values.mean()
-    zeros = torch.zeros(values.shape).cuda()
-    filtered = torch.where(values > mean, values, zeros)
+    data = torch.randn((1, 5, 2048, 50, 50)).cuda()
+    model = TransformerMIL(n_classes=2, in_features=2048, out_features=512).cuda()
+    model.eval()
+    # print(model.eval())
+    # logits, attn = model(data)
+    # cls_attention = attn[:,:, 0, :6000]
+    # values, indices = torch.max(cls_attention, 1)
+    # mean = values.mean()
+    # zeros = torch.zeros(values.shape).cuda()
+    # filtered = torch.where(values > mean, values, zeros)
     
     # filter = values > values.mean()
     # filtered_values = values[filter]
     # values = np.where(values>values.mean(), values, 0)
+    output = model(data)
+    print(output.shape)
 
-    print(filtered.shape)
+    # print(filtered.shape)
 
 
     # values = [v if v > values.mean().item() else 0 for v in values]
diff --git a/code/models/__pycache__/CTMIL.cpython-39.pyc b/code/models/__pycache__/CTMIL.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..eda55e7b164943a38840c01ec5d3ec301e2a30d1
Binary files /dev/null and b/code/models/__pycache__/CTMIL.cpython-39.pyc differ
diff --git a/code/models/__pycache__/TransMIL.cpython-39.pyc b/code/models/__pycache__/TransMIL.cpython-39.pyc
index cdfdf33bcbe096c18e1f013197fc1aa0a4042682..98c9da40a501e05173a1df7f4e2f9e9af88782bd 100644
Binary files a/code/models/__pycache__/TransMIL.cpython-39.pyc and b/code/models/__pycache__/TransMIL.cpython-39.pyc differ
diff --git a/code/models/__pycache__/TransformerMIL.cpython-39.pyc b/code/models/__pycache__/TransformerMIL.cpython-39.pyc
index da33d0418c6c9204aea2231c72e8846914e14139..f62cb699a75354df064dbe29611e64a7a61b8edd 100644
Binary files a/code/models/__pycache__/TransformerMIL.cpython-39.pyc and b/code/models/__pycache__/TransformerMIL.cpython-39.pyc differ
diff --git a/code/models/__pycache__/_transformer.cpython-39.pyc b/code/models/__pycache__/_transformer.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a49af6b6719865c3c00d9229c68e87cc882d8f91
Binary files /dev/null and b/code/models/__pycache__/_transformer.cpython-39.pyc differ
diff --git a/code/models/__pycache__/model_interface.cpython-39.pyc b/code/models/__pycache__/model_interface.cpython-39.pyc
index 243c3f2980570e3f84143dbeb71e3a1a48e10942..94d968ec9a86cee4a1fe647ec37d16792501b3f8 100644
Binary files a/code/models/__pycache__/model_interface.cpython-39.pyc and b/code/models/__pycache__/model_interface.cpython-39.pyc differ
diff --git a/code/models/__pycache__/model_interface_classic.cpython-39.pyc b/code/models/__pycache__/model_interface_classic.cpython-39.pyc
index 23e18e285c2185a51b7c8325683a0311412a4b1e..97f4c31267274e649d3ec363c906f91630c09ca7 100644
Binary files a/code/models/__pycache__/model_interface_classic.cpython-39.pyc and b/code/models/__pycache__/model_interface_classic.cpython-39.pyc differ
diff --git a/code/models/_transformer.py b/code/models/_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..232f70cfd42b4673c266d1999ecf72773e127c5f
--- /dev/null
+++ b/code/models/_transformer.py
@@ -0,0 +1,100 @@
+import torch
+from einops import rearrange
+from torch import nn
+
+
+class PreNorm(nn.Module):
+    def __init__(self, dim, fn):
+        super().__init__()
+        self.norm = nn.LayerNorm(dim)
+        self.fn = fn
+
+    def forward(self, x, **kwargs):
+        return self.fn(self.norm(x), **kwargs)
+
+
+class Attention(nn.Module):
+    def __init__(self, dim=512, heads=8, dim_head=512 // 8, dropout=0.1):
+        super().__init__()
+        inner_dim = dim_head * heads
+        project_out = not (heads == 1 and dim_head == dim)
+
+        self.heads = heads
+        self.scale = dim_head ** -0.5
+
+        self.attend = nn.Softmax(dim=-1)
+        self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)
+
+        self.to_out = nn.Sequential(
+            nn.Linear(inner_dim, dim),
+            nn.Dropout(dropout)
+        ) if project_out else nn.Identity()
+
+    def forward(self, x):
+        qkv = self.to_qkv(x).chunk(3, dim=-1)
+        q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h=self.heads), qkv)
+
+        dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
+
+        attn = self.attend(dots)
+
+        out = torch.matmul(attn, v)
+        out = rearrange(out, 'b h n d -> b n (h d)')
+        return self.to_out(out)
+
+
+class FeedForward(nn.Module):
+    def __init__(self, dim=512, hidden_dim=1024, dropout=0.1):
+        super().__init__()
+        self.net = nn.Sequential(
+            nn.Linear(dim, hidden_dim),
+            nn.GELU(),
+            nn.Dropout(dropout),
+            nn.Linear(hidden_dim, dim),
+            nn.Dropout(dropout)
+        )
+
+    def forward(self, x):
+        return self.net(x)
+
+
+class TransformerLayer(nn.Module):
+    def __init__(self, norm_layer=nn.LayerNorm, dim=512, heads=8, use_ff=True, use_norm=True):
+        super().__init__()
+        self.norm = norm_layer(dim)
+        self.attn = Attention(dim=dim, heads=heads, dim_head=dim // heads)
+        self.use_ff = use_ff
+        self.use_norm = use_norm
+        if self.use_ff:
+            self.ff = FeedForward()
+
+    def forward(self, x):
+        if self.use_norm:
+            x = x + self.attn(self.norm(x))
+        else:
+            x = x + self.attn(x)
+        if self.use_ff:
+            x = self.ff(x) + x
+        return x
+
+
+class Transformer(nn.Module):
+    def __init__(self, num_classes):
+        super().__init__()
+        self.n_classes = num_classes
+
+        self._fc1 = nn.Sequential(nn.Linear(2048, 512, bias=True), nn.ReLU())
+        self.layer1 = TransformerLayer(dim=512, heads=8, use_ff=False, use_norm=True)
+        self.layer2 = TransformerLayer(dim=512, heads=8, use_ff=False, use_norm=True)
+        self._fc2 = nn.Linear(512, self.n_classes, bias=True)
+
+    def forward(self, x,_):
+
+        h = x
+        h = self._fc1(h)
+        h = self.layer1(h)
+        h = self.layer2(h)
+        h = h.mean(dim=1)
+        logits = self._fc2(h)
+
+        return logits
\ No newline at end of file
diff --git a/code/models/ckpt/simclr_e25.ckpt b/code/models/ckpt/simclr_e25.ckpt
new file mode 100644
index 0000000000000000000000000000000000000000..7543d0ccda3a80f0018e6c23be1799294d440226
Binary files /dev/null and b/code/models/ckpt/simclr_e25.ckpt differ
diff --git a/code/models/model_interface.py b/code/models/model_interface.py
index 43494c2101b03d1fb6ae24c6c0d4b289b1becf2f..155c1c890da28effd9ee09ba24aca6419624347c 100755
--- a/code/models/model_interface.py
+++ b/code/models/model_interface.py
@@ -104,13 +104,30 @@ class ModelInterface(pl.LightningModule):
         super(ModelInterface, self).__init__()
         self.save_hyperparameters() #ignore=kargs.keys()
         self.n_classes = model.n_classes
+        self.lr = optimizer.lr
+        # if 'in_features' in kargs.keys():
+        #     self.in_features = kargs['in_features']
+        # else: self.in_features = 2048
+        # print(self.in_features)
+        # print(model.in_features)
+        self.in_features = model.in_features
+        # self.bag_size = int(kargs['bag_size'])
+        if 'bag_size' in kargs.keys():
+            self.bag_size = int(kargs['bag_size'])
+        else: self.bag_size = 200
         
         if model.name == 'AttTrans':
             self.model = milmodel.MILModel(num_classes=self.n_classes, pretrained=True, mil_mode='att_trans')
         elif model.name == 'vit':
             self.model = timm.create_model('vit_base_patch16_224', pretrained=False, num_classes=self.n_classes)
             self.model.patch_embed = nn.Sequential(nn.Linear(self.in_features, 768), nn.Identity())
-
+        elif model.name == 'resnet50':
+            self.model = models.resnet50(weights='IMAGENET1K_V1')
+            self.model.conv1 = torch.nn.Conv2d(self.in_features, 64, kernel_size=(7,7), stride=(2,2), padding=(3,3))
+            # print(self.model)
+            self.model.fc = torch.nn.Sequential(
+                torch.nn.Linear(self.model.fc.in_features, self.n_classes),
+            )
         else: self.load_model()
         if self.n_classes>2:
             # self.aucm_loss = AUCM_MultiLabel(num_classes = self.n_classes, device=self.device)
@@ -124,14 +141,11 @@ class ModelInterface(pl.LightningModule):
 
         self.model_name = model.name
         
-        
         self.optimizer = optimizer
         # print(kargs)
         self.save_path = kargs['log']
 
-        if 'in_features' in kargs.keys():
-            self.in_features = kargs['in_features']
-        else: self.in_features = 2048
+        
         # # self.out_features = kargs['out_features']
         # self.in_features = 2048
         self.out_features = 512
@@ -230,8 +244,9 @@ class ModelInterface(pl.LightningModule):
         elif self.backbone == 'resnet50':
             
             self.model_ft = resnet50_baseline(pretrained=True)
-            for param in self.model_ft.parameters():
-                param.requires_grad = False
+            # self.model_ft.fc = torch.linear()
+            # for param in self.model_ft.parameters():
+            #     param.requires_grad = False
 
             
         elif self.backbone == 'efficientnet':
@@ -256,17 +271,22 @@ class ModelInterface(pl.LightningModule):
                 nn.Linear(53*53*50, 1024),
                 nn.ReLU(),
             )
+
+        # print('Bag_size: ', self.bag_size)
         if self.model_ft:
-            self.example_input_array = torch.rand([1,1000,3,224,224])
+            self.example_input_array = torch.rand([1,self.bag_size,3,224,224])
+        elif self.model_name == 'resnet50' or self.model_name == 'CTMIL':
+            self.example_input_array = torch.rand([5,self.in_features,50,50])
+        
         else:
-            self.example_input_array = torch.rand([1,1000,self.in_features])
+            self.example_input_array = torch.rand([1,self.bag_size,self.in_features])
+        # self.example_input_array = torch.rand([1,self.bag_size,self.in_features])
 
         self.train_step_outputs = []
         self.validation_step_outputs = []
         self.test_step_outputs = []
 
     def forward(self, x):
-        # print(x.shape)
         if self.model_name == 'AttTrans' or self.model_name == 'MonaiMILModel':
             return self.model(x)
         if self.model_ft:
@@ -283,17 +303,18 @@ class ModelInterface(pl.LightningModule):
         else: 
             feats = x.unsqueeze(0)
         del x
+        if self.model_name == 'resnet50':
+            feats = feats.squeeze(0)
         return self.model(feats)
         # return self.model(x)
 
     def step(self, input):
 
-        
         input = input.float()
         logits = self(input.contiguous())
         Y_hat = torch.argmax(logits, dim=1)
-        # Y_prob = F.softmax(logits, dim = 1)
-        Y_prob = torch.sigmoid(logits)
+        Y_prob = F.softmax(logits, dim = 1)
+        # Y_prob = torch.sigmoid(logits)
 
 
         # Y_hat = torch.argmax(logits, dim=1)
@@ -303,7 +324,10 @@ class ModelInterface(pl.LightningModule):
 
     def training_step(self, batch):
 
-        input, label, _= batch
+        # print()
+        # print(batch)
+        # print(len(batch))
+        input, label, _ = batch
 
 
         logits, Y_prob, Y_hat = self.step(input) 
@@ -453,14 +477,18 @@ class ModelInterface(pl.LightningModule):
         patients = [item for sublist in patients for item in sublist]
         loss = torch.stack([x['loss'] for x in self.validation_step_outputs])
         
-        self.log_dict(self.val_metrics(max_probs.squeeze(), target.squeeze()),
-                          on_epoch = True, logger = True, sync_dist=True)
+        # if len(max_probs.shape) <2:
+        #     max_probs = max_probs.unsqueeze(0).unsqueeze(0)
+        #     target = target.unsqueeze(0).unsqueeze(0)
+        
+        # self.log_dict(self.val_metrics(max_probs.squeeze(0), target.squeeze(0)),
+        #                   on_epoch = True, logger = True, sync_dist=True)
 
         if self.n_classes <=2:
             out_probs = probs[:,1] 
         else: out_probs = probs
 
-        self.log_confusion_matrix(out_probs, target, stage='val', comment='slide')
+        # self.log_confusion_matrix(out_probs, target, stage='val', comment='slide')
         if len(target.unique()) != 1:
             self.log('val_auc', self.AUROC(out_probs, target.squeeze()).mean(), prog_bar=True, on_epoch=True, logger=True, sync_dist=True)
         else:    
@@ -529,13 +557,15 @@ class ModelInterface(pl.LightningModule):
         if self.n_classes <=2:
             patient_score = patient_score[:,1] 
 
-        self.log_confusion_matrix(patient_score, patient_target, stage='val', comment='patient')
+        # self.log_confusion_matrix(patient_score, patient_target, stage='val', comment='patient')
         
         # print(patient_score)
         # print(patient_target)
         # print(patient_target.squeeze())
         # print(self.AUROC(patient_score, patient_target.squeeze()))
         
+        # print('patient_score: ', patient_score)
+        # print('patient_target: ', patient_target)
         
 
         # self.log_roc_curve(patient_score, patient_target.squeeze(), stage='val', comment='patient')
@@ -684,14 +714,30 @@ class ModelInterface(pl.LightningModule):
                 score.append(probs)
             
             score = torch.stack(score)
+            # print(score)
             if self.n_classes <= 2:
                 positive_positions = (score.argmax(dim=1) == 1).nonzero().squeeze()
                 if positive_positions.numel() != 0:
                     score = score[positive_positions]
+            # elif self.n_classes > 2: 
+            #     positive_positions = (score.argmax(dim=1) > 0).nonzero().squeeze()
+            #     if positive_positions.numel() == 1:
+            #         score = score[positive_positions]
+            #     else: 
+
+            #         values, indices = score[positive_positions].max(dim=1)
+            #         values = values.squeeze().argmax()
+            #         score = score[positive_positions[]]
+            # positive_positions = (score.argmax(dim=1) > 0).nonzero().squeeze()
+            # if positive_positions.numel() != 0:
+            #     score = score[positive_positions]
+
                 
             if len(score.shape) > 1:
                 
+                # print('before: ', score)
                 score = torch.mean(score, dim=0) #.cpu().detach().numpy()
+                # print('after: ', score)
 
             patient_score.append(score)  
             
@@ -748,8 +794,8 @@ class ModelInterface(pl.LightningModule):
         self.log_confusion_matrix(patient_score, patient_target, stage='test', comment='patient')
         # log roc curve
 
-        print(patient_score.shape)
-        print(patient_target.shape)
+        # print(patient_score.shape)
+        # print(patient_target.shape)
         self.log_roc_curve(patient_score, patient_target.squeeze(), stage='test', comment='patient')
         # log pr curve
         self.log_pr_curve(patient_score, patient_target.squeeze(), stage='test')
@@ -854,7 +900,7 @@ class ModelInterface(pl.LightningModule):
     
         for v in label_mapping.values():
             slide_output_dict[v] = []
-        print(slide_output_dict)
+        # print(slide_output_dict)
         for p, t in zip(list(complete_patient_dict.keys()), patient_target):
             # print(complete_patient_dict[p])
             # target_label = label_mapping[str(t.item())]?
@@ -906,6 +952,7 @@ class ModelInterface(pl.LightningModule):
             optimal_threshold [Float]
         '''
 
+
         youden_j = tpr - fpr
         optimal_idx = torch.argmax(youden_j)
         # print(youden_j[optimal_idx])
@@ -916,7 +963,7 @@ class ModelInterface(pl.LightningModule):
 
         return optimal_fpr, optimal_tpr, optimal_threshold
 
-    def log_topk_patients(self, patient_list, patient_scores, patient_target, thresh=[], stage='val',  k=10):
+    def log_topk_patients(self, patient_list, patient_scores, patient_target, thresh=[], stage='val',  k=5):
         
         # patient_target = np.array([i.item() for i in patient_target])
         patient_target = torch.Tensor(patient_target)
@@ -927,11 +974,9 @@ class ModelInterface(pl.LightningModule):
 
         for n in range(self.n_classes):
 
-            # print(n)
-
             n_patients = patient_list[patient_target == n]
             n_scores = [s[n] for s in patient_scores[patient_target == n]]
-            print(n_patients)
+            # print(n_patients)
 
             topk_csv_path = f'{self.loggers[0].log_dir}/{stage}_c{n}_top_patients.csv'
 
@@ -950,8 +995,10 @@ class ModelInterface(pl.LightningModule):
 
     def load_thresholds(self, probs, target, stage, comment=''):
         threshold_csv_path = f'{self.loggers[0].log_dir}/val_thresholds.csv'
+        optimal_threshold = 1/self.n_classes
         if not Path(threshold_csv_path).is_file():
-            thresh_df = pd.DataFrame({'slide': [0.5], 'patient': [0.5]})
+            
+            thresh_df = pd.DataFrame({'slide': [optimal_threshold], 'patient': [optimal_threshold]})
             thresh_df.to_csv(threshold_csv_path, index=False)
 
         thresh_df = pd.read_csv(threshold_csv_path)
@@ -960,12 +1007,13 @@ class ModelInterface(pl.LightningModule):
                 fpr_list, tpr_list, thresholds = self.ROC(probs, target)
                 optimal_fpr, optimal_tpr, optimal_threshold = self.get_optimal_operating_point(fpr_list, tpr_list, thresholds)
                 print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
-                thresh_df.at[0, comment] =  optimal_threshold
-                thresh_df.to_csv(threshold_csv_path, index=False)
+                
             else: 
-                optimal_threshold = 0.5
+                optimal_threshold = 1/self.n_classes
+            # thresh_df.at[0, comment] =  optimal_threshold
+            # thresh_df.to_csv(threshold_csv_path, index=False)
+
         elif stage == 'test': 
-            
             optimal_threshold = thresh_df.at[0, comment]
             print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
 
@@ -979,6 +1027,9 @@ class ModelInterface(pl.LightningModule):
 
         # read threshold file
         threshold_csv_path = f'{self.loggers[0].log_dir}/val_thresholds.csv'
+        # print(self.loggers[0].log_dir)
+        # print(threshold_csv_path)
+        
         if not Path(threshold_csv_path).is_file():
             # thresh_dict = {'index': ['train', 'val'], 'columns': , 'data': [[0.5, 0.5], [0.5, 0.5]]}
             thresh_df = pd.DataFrame({'slide': [0.5], 'patient': [0.5]})
@@ -993,7 +1044,10 @@ class ModelInterface(pl.LightningModule):
                 thresh_df.at[0, comment] =  optimal_threshold
                 thresh_df.to_csv(threshold_csv_path, index=False)
             else: 
-                optimal_threshold = 0.5
+                # fpr_list, tpr_list, thresholds = multiclass_roc(probs, target)
+                # optimal_fpr, optimal_tpr, optimal_threshold = self.get_optimal_operating_point(fpr_list, tpr_list, thresholds)
+
+                optimal_threshold = 1/self.n_classes
         elif stage == 'test': 
             
             optimal_threshold = thresh_df.at[0, comment]
diff --git a/code/models/model_interface_classic.py b/code/models/model_interface_classic.py
index d068487b10963401434a4fc36fac0643cd6f96f0..dcb18d17f366833b5055f199f2cc3d200617efb7 100644
--- a/code/models/model_interface_classic.py
+++ b/code/models/model_interface_classic.py
@@ -147,6 +147,8 @@ class ModelInterface_Classic(pl.LightningModule):
         #---->Metrics
         if self.n_classes > 2: 
             self.AUROC = torchmetrics.AUROC(task='multiclass', num_classes = self.n_classes, average=None)
+            self.accuracy = torchmetrics.Accuracy(task='multiclass', num_classes = self.n_classes, average='weighted')
+
             self.PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = self.n_classes)
             self.ROC = torchmetrics.ROC(task='multiclass', num_classes=self.n_classes)
             self.confusion_matrix = torchmetrics.ConfusionMatrix(task='multiclass', num_classes = self.n_classes) 
@@ -164,6 +166,8 @@ class ModelInterface_Classic(pl.LightningModule):
                                                                             
         else : 
             self.AUROC = torchmetrics.AUROC(task='binary')
+            self.accuracy = torchmetrics.Accuracy(task='binary')
+
             # self.AUROC = torchmetrics.AUROC(num_classes=self.n_classes, average = 'weighted')
             self.PRC = torchmetrics.PrecisionRecallCurve(task='binary')
             self.ROC = torchmetrics.ROC(task='binary')
@@ -298,6 +302,10 @@ class ModelInterface_Classic(pl.LightningModule):
                 nn.Linear(53*53, self.out_features),
                 nn.ReLU(),
             )
+        
+        self.train_step_outputs = []
+        self.validation_step_outputs = []
+        self.test_step_outputs = []
 
     # def __build_
 
@@ -350,14 +358,15 @@ class ModelInterface_Classic(pl.LightningModule):
 
         self.log('loss', loss, prog_bar=True, on_epoch=True, logger=True, batch_size=1, sync_dist=True)
 
-        return {'loss': loss, 'Y_prob': Y_prob, 'Y_hat': Y_hat, 'label': label} 
+        self.train_step_outputs.append({'loss': loss, 'Y_prob': Y_prob, 'Y_hat': Y_hat, 'label': label})
+        return loss
 
-    def training_epoch_end(self, training_step_outputs):
+    def on_training_epoch_end(self):
 
         # logits = torch.cat([x['logits'] for x in training_step_outputs], dim = 0)
-        probs = torch.cat([x['Y_prob'] for x in training_step_outputs])
-        max_probs = torch.cat([x['Y_hat'] for x in training_step_outputs])
-        target = torch.cat([x['label'] for x in training_step_outputs])
+        probs = torch.cat([x['Y_prob'] for x in self.train_step_outputs])
+        max_probs = torch.cat([x['Y_hat'] for x in self.train_step_outputs])
+        target = torch.cat([x['label'] for x in self.train_step_outputs])
 
         # probs = torch.cat([x['Y_prob'] for x in training_step_outputs])
         # probs = torch.stack([x['Y_prob'] for x in training_step_outputs], dim=0)
@@ -415,28 +424,29 @@ class ModelInterface_Classic(pl.LightningModule):
         # print(Y_hat)
         # print(label)
         # self.log('val_aucm_loss', aucm_loss, prog_bar=True, on_epoch=True, logger=True, batch_size=1, sync_dist=True)
-        return {'logits' : logits, 'Y_prob' : Y_prob, 'Y_hat' : Y_hat, 'label' : label, 'name': wsi_name, 'patient': patient, 'tile_name': tile_name, 'loss': loss}
+        self.validation_step_outputs.append({'logits' : logits, 'Y_prob' : Y_prob, 'Y_hat' : Y_hat, 'label' : label, 'name': wsi_name, 'patient': patient, 'tile_name': tile_name, 'loss': loss})
+        
 
 
-    def validation_epoch_end(self, val_step_outputs):
+    def on_validation_epoch_end(self):
         
-        logits = torch.cat([x['logits'] for x in val_step_outputs], dim = 0)
-        probs = torch.cat([x['Y_prob'] for x in val_step_outputs])
-        max_probs = torch.cat([x['Y_hat'] for x in val_step_outputs])
-        target = torch.cat([x['label'] for x in val_step_outputs])
+        logits = torch.cat([x['logits'] for x in self.validation_step_outputs], dim = 0)
+        probs = torch.cat([x['Y_prob'] for x in self.validation_step_outputs])
+        max_probs = torch.cat([x['Y_hat'] for x in self.validation_step_outputs])
+        target = torch.cat([x['label'] for x in self.validation_step_outputs])
         # slide_names = [list(x['name']) for x in val_step_outputs]
         slide_names = []
-        for x in val_step_outputs:
+        for x in self.validation_step_outputs:
             slide_names += list(x['name'])
         # patients = [list(x['patient']) for x in val_step_outputs]
         patients = []
-        for x in val_step_outputs:
+        for x in self.validation_step_outputs:
             patients += list(x['patient'])
         tile_name = []
-        for x in val_step_outputs:
+        for x in self.validation_step_outputs:
             tile_name += list(x['tile_name'])
 
-        loss = torch.stack([x['loss'] for x in val_step_outputs])
+        loss = torch.stack([x['loss'] for x in self.validation_step_outputs])
 
         self.log_dict(self.valid_metrics(max_probs.squeeze(), target.squeeze()),
                           on_epoch = True, logger = True, sync_dist=True)
@@ -445,6 +455,8 @@ class ModelInterface_Classic(pl.LightningModule):
             out_probs = probs[:,1] 
         else: out_probs = probs
 
+        self.log('val_accuracy', self.accuracy(out_probs, target), prog_bar=True, on_epoch=True, logger=True, sync_dist=True)
+
         self.log_confusion_matrix(out_probs, target, stage='val', comment='slide')
         if len(target.unique()) != 1:
             self.log('val_auc', self.AUROC(out_probs, target).squeeze().mean(), prog_bar=True, on_epoch=True, logger=True, sync_dist=True)
@@ -505,6 +517,7 @@ class ModelInterface_Classic(pl.LightningModule):
         self.log_pr_curve(patient_score, patient_target.squeeze(), stage='val', comment='patient')
 
         
+        
         if len(patient_target.unique()) != 1:
             self.log('val_patient_auc', self.AUROC(patient_score, patient_target.squeeze()).mean(), prog_bar=True, on_epoch=True, logger=True, sync_dist=True)
         else:    
@@ -516,8 +529,7 @@ class ModelInterface_Classic(pl.LightningModule):
             
 
         # precision, recall, thresholds = self.PRC(probs, target)
-
-        
+        # self.log('val_accuracy', self.accuracy(patient_score, patient_target), prog_bar=True, on_epoch=True, logger=True, sync_dist=True)
 
         #---->acc log
         for c in range(self.n_classes):
@@ -557,19 +569,19 @@ class ModelInterface_Classic(pl.LightningModule):
         # self.data[Y]["correct"] += (int(Y_hat) == Y)
         # self.data[Y]["correct"] += (Y_hat.item() == Y)
 
-        return {'logits' : logits, 'Y_prob' : Y_prob, 'Y_hat' : Y_hat, 'label' : label, 'name': wsi_name, 'patient': patient, 'tile_name': batch_names}
+        self.test_step_outputs.append({'logits' : logits, 'Y_prob' : Y_prob, 'Y_hat' : Y_hat, 'label' : label, 'name': wsi_name, 'patient': patient, 'tile_name': batch_names})
 
-    def test_epoch_end(self, output_results):
+    def on_test_epoch_end(self):
 
-        logits = torch.cat([x['logits'] for x in output_results], dim = 0)
-        probs = torch.cat([x['Y_prob'] for x in output_results])
-        max_probs = torch.cat([x['Y_hat'] for x in output_results])
-        target = torch.cat([x['label'] for x in output_results])
+        logits = torch.cat([x['logits'] for x in self.test_step_outputs], dim = 0)
+        probs = torch.cat([x['Y_prob'] for x in self.test_step_outputs])
+        max_probs = torch.cat([x['Y_hat'] for x in self.test_step_outputs])
+        target = torch.cat([x['label'] for x in self.test_step_outputs])
         slide_names = []
-        for x in output_results:
+        for x in self.test_step_outputs:
             slide_names += list(x['name'])
         patients = []
-        for x in output_results:
+        for x in self.test_step_outputs:
             patients += list(x['patient'])
         tile_name = []
         # for x in output_results:
@@ -892,8 +904,8 @@ class ModelInterface_Classic(pl.LightningModule):
             
         else:
             confmat = confusion_matrix(probs, target, task='multiclass', num_classes=self.n_classes)
-        print(stage, comment)
-        print(confmat)
+        # print(stage, comment)
+        # print(confmat)
         cm_labels = LABEL_MAP[self.task].values()
 
         fig, ax = plt.subplots()
diff --git a/code/test.ipynb b/code/test.ipynb
index 4149f989b13e724c9ac6f209b4b3ecc887f80b97..ceedf6ac6b348f3fd4ad37d174939b373cf77ee8 100644
--- a/code/test.ipynb
+++ b/code/test.ipynb
@@ -7,9 +7,16 @@
    "outputs": [],
    "source": [
     "import numpy as np\n",
+    "from pathlib import Path\n",
     "\n",
-    "a = [None] * 500\n",
-    "print(a.shape)"
+    "cohort_root = '/homeStor1/ylan/data/DeepGraft/224_256uM_annotated/DEEPGRAFT_RU/BLOCKS'\n",
+    "\n",
+    "l = len([Path(cohort_root).iterdir()])\n",
+    "for i in Path(cohort_root).iterdir():\n",
+    "    \n",
+    "print(l/7)\n",
+    "\n",
+    "\n"
    ]
   },
   {
@@ -30,4 +37,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 2
-}
\ No newline at end of file
+}
diff --git a/code/train.py b/code/train.py
index 980f5f4d1902cc58a39e779cbc9b4cc9211e7c16..fbca79fe741faff7467a2e8ab050a48cc0a0a4e2 100644
--- a/code/train.py
+++ b/code/train.py
@@ -18,6 +18,8 @@ import pytorch_lightning as pl
 from pytorch_lightning import Trainer
 from pytorch_lightning.strategies import DDPStrategy
 import torch
+from pytorch_lightning.callbacks import DeviceStatsMonitor
+from pytorch_lightning.tuner import Tuner
 # from train_loop import KFoldLoop
 # from pytorch_lightning.plugins.training_type import DDPPlugin
 
@@ -65,7 +67,7 @@ def make_parse():
     parser.add_argument('--stage', default='train', type=str)
     parser.add_argument('--config', default='DeepGraft/TransMIL.yaml',type=str)
     parser.add_argument('--version', default=2,type=int)
-    parser.add_argument('--epoch', default='0',type=str)
+    parser.add_argument('--epoch', default=None,type=str)
 
     parser.add_argument('--gpus', nargs='+', default = [0], type=int)
     parser.add_argument('--loss', default = 'CrossEntropyLoss', type=str)
@@ -76,6 +78,7 @@ def make_parse():
     parser.add_argument('--label_file', type=str)
     # parser.add_argument('--from_ft', action='store_true')
     parser.add_argument('--fine_tune', action='store_true')
+    parser.add_argument('--fast_dev_run', action='store_true')
     
 
     args = parser.parse_args()
@@ -109,7 +112,7 @@ def main(cfg):
     home = Path.cwd().parts[1]
 
     train_classic = False
-    if cfg.Model.name in ['inception', 'resnet18', 'resnet50', 'vit', 'efficientnet']:
+    if cfg.Model.name in ['inception', 'resnet18', 'vit', 'efficientnet']:
         train_classic = True
         use_features = False
 
@@ -118,6 +121,8 @@ def main(cfg):
     # elif cfg.Model.backbone == 'simple':
     #     use_features = False
     else: use_features = False
+
+    # print(cfg.Data.bag_size)
     
     DataInterface_dict = {
                 'data_root': cfg.Data.data_dir,
@@ -132,6 +137,8 @@ def main(cfg):
                 'cache': cfg.Data.cache,
                 'train_classic': train_classic,
                 'model_name': cfg.Model.name,
+                'in_features': cfg.Model.in_features,
+                'feature_extractor': cfg.Data.feature_extractor,
                 }
 
     if cfg.Data.cross_val:
@@ -139,7 +146,6 @@ def main(cfg):
     else: dm = MILDataModule(**DataInterface_dict)
     
     #---->Define Model
-    
     ModelInterface_dict = {'model': cfg.Model,
                             'loss': cfg.Loss,
                             'optimizer': cfg.Optimizer,
@@ -149,6 +155,8 @@ def main(cfg):
                             'task': cfg.task,
                             'in_features': cfg.Model.in_features,
                             'out_features': cfg.Model.out_features,
+                            'bag_size': cfg.Data.bag_size,
+                            # 'batch_size': cfg.Data.train_dataloader.batch_size,
                             }
 
     if train_classic:
@@ -168,7 +176,7 @@ def main(cfg):
             logger=cfg.load_loggers,
             callbacks=cfg.callbacks,
             max_epochs= cfg.General.epochs,
-            min_epochs = 500,
+            min_epochs = 100,
             accelerator='gpu',
             # strategy='ddp',
             # plugins=plugins,
@@ -181,12 +189,12 @@ def main(cfg):
             use_distributed_sampler=False,
             enable_progress_bar=True,
             gradient_clip_val=0.0,
-            # fast_dev_run = True,
+            fast_dev_run = cfg.fast_dev_run,
             # limit_train_batches=1,
             
             # deterministic=True,
             accumulate_grad_batches=10,
-            check_val_every_n_epoch=5,
+            check_val_every_n_epoch=1,
         )
     else:
         trainer = Trainer(
@@ -195,24 +203,26 @@ def main(cfg):
             logger=cfg.load_loggers,
             callbacks=cfg.callbacks,
             max_epochs= cfg.General.epochs,
-            min_epochs = 150,
+            # max_epochs= 2,
+            min_epochs = 100,
 
             # gpus=cfg.General.gpus,
             accelerator='gpu',
             devices=cfg.General.gpus,
-            # amp_backend='native',
-            # amp_level=cfg.General.amp_level,  
-            precision='16-mixed',  
-            # precision=cfg.General.precision,  
+            # precision='16-mixed',  
+            precision=cfg.General.precision,  
             accumulate_grad_batches=cfg.General.grad_acc,
             gradient_clip_val=0.0,
-            log_every_n_steps=10,
-            # fast_dev_run = True,
+            # log_every_n_steps=10,
+            fast_dev_run = cfg.fast_dev_run,
             # limit_train_batches=1,
             
             # deterministic=True,
             # num_sanity_val_steps=0,
-            check_val_every_n_epoch=5,
+            check_val_every_n_epoch=1,
+            log_every_n_steps=20,
+            # profiler='simple',
+
         )
     # print(cfg.log_path)
     # print(trainer.loggers[0].log_dir)
@@ -220,24 +230,24 @@ def main(cfg):
     #----> Copy Code
 
     # home = Path.cwd()[0]
-
-    if cfg.General.server == 'train':
-
-        copy_path = Path(trainer.loggers[0].log_dir) / 'code'
-        copy_path.mkdir(parents=True, exist_ok=True)
-        copy_origin = '/' / Path('/'.join(cfg.log_path.parts[1:5])) / 'code'
-        shutil.copytree(copy_origin, copy_path, dirs_exist_ok=True)
+    # comment out for fast_dev_run because no logger is initiated
+    if not cfg.fast_dev_run:
+        if cfg.General.server == 'train':
+            copy_path = Path(trainer.loggers[0].log_dir) / 'code'
+            copy_path.mkdir(parents=True, exist_ok=True)
+            copy_origin = '/' / Path('/'.join(cfg.log_path.parts[1:5])) / 'code'
+            shutil.copytree(copy_origin, copy_path, dirs_exist_ok=True)
 
     #---->train or test
-    # if cfg.resume_training:
-    #     last_ckpt = Path(cfg.log_path) / 'lightning_logs' / f'version_{cfg.version}' / 'last.ckpt'
-    #     print('Resume Training from: ', last_ckpt)
-    #     model = model.load_from_checkpoint(checkpoint_path=last_ckpt, cfg=cfg)
-    #     # trainer.fit(model = model, ckpt_path=last_ckpt) #, datamodule = dm
-    #     trainer.fit(model, dm)
+    if cfg.resume_training:
+        last_ckpt = Path(cfg.log_path) / 'lightning_logs' / f'version_{cfg.version}' / 'last.ckpt'
+        print('Resume Training from: ', last_ckpt)
+        model = model.load_from_checkpoint(checkpoint_path=last_ckpt, cfg=cfg)
+        # trainer.fit(model = model, ckpt_path=last_ckpt) #, datamodule = dm
+        trainer.fit(model, dm)
     # print(cfg.resume_training)
 
-    if cfg.General.server == 'train':
+    if cfg.General.server == 'train' or cfg.General.server == 'fine_tune':
 
         # k-fold cross validation loop
         if cfg.Data.cross_val: 
@@ -252,6 +262,9 @@ def main(cfg):
             # trainer.fit(model = model, ckpt_path=last_ckpt) #, datamodule = dm
             trainer.fit(model, dm)
         else:                                                   
+            # tuner = Tuner(trainer)
+            # tuner.scale_batch_size(model, datamodule=dm)
+            # tuner.lr_find(model, datamodule=dm)
             trainer.fit(model = model, datamodule = dm)
             # trainer.test(model = model, datamodule = dm)
     else:
@@ -262,24 +275,49 @@ def main(cfg):
 
         model_paths = list(log_path.glob('*.ckpt'))
 
-        if cfg.epoch == 'last':
+
+        if not cfg.epoch:
+            model_paths = [str(model_path) for model_path in model_paths if f'.ckpt' in str(model_path)]
+        elif cfg.epoch == 'last':
             model_paths = [str(model_path) for model_path in model_paths if f'last' in str(model_path)]
         elif int(cfg.epoch) < 10:
             cfg.epoch = f'0{cfg.epoch}'
-        
         else:
             model_paths = [str(model_path) for model_path in model_paths if f'epoch={cfg.epoch}' in str(model_path)]
         # model_paths = [f'{log_path}/epoch=279-val_loss=0.4009.ckpt']
         # print(model_paths)
         
-        # for path in model_paths:
-        path  = model_paths[0]
-            # print(path)
-        model = model.load_from_checkpoint(checkpoint_path=path, cfg=cfg)
-        if cfg.General.server == 'val':
-            trainer.validate(model=model, datamodule=dm)
-        elif cfg.General.server == 'test':
-            trainer.test(model=model, datamodule=dm)
+        for path in model_paths:
+        # path  = model_paths[0]
+            if 'last' in path:
+                epoch = 'last'
+            else:
+                name = Path(path).stem
+                epoch = name.split('-')[0].split('=')[1]
+            # print(int(Path(path).stem.split('-')[0].split('=')[1]))
+            cfg.epoch = epoch
+            # print(cfg)
+            cfg.callbacks = load_callbacks(cfg, save_path)
+            # print(cfg)
+            # print(trainer.callbacks)
+            cfg.load_loggers = load_loggers(cfg)
+            trainer = Trainer(
+                logger=cfg.load_loggers,
+                callbacks=cfg.callbacks,
+                max_epochs= cfg.General.epochs,
+                min_epochs = 100,
+                accelerator='gpu',
+                devices=cfg.General.gpus,
+                precision=cfg.General.precision,  
+                accumulate_grad_batches=cfg.General.grad_acc,
+                gradient_clip_val=0.0,
+            )
+            # # print('Loading from: ', path)
+            model = model.load_from_checkpoint(checkpoint_path=path, cfg=cfg)
+            if cfg.General.server == 'val':
+                trainer.validate(model=model, datamodule=dm)
+            elif cfg.General.server == 'test':
+                trainer.test(model=model, datamodule=dm)
 
 
 def check_home(cfg):
@@ -321,6 +359,8 @@ if __name__ == '__main__':
     cfg.version = args.version
     cfg.fine_tune = args.fine_tune
     cfg.resume_training = args.resume_training
+    cfg.fast_dev_run = args.fast_dev_run
+    
 
     if args.label_file: 
         cfg.Data.label_file = '/home/ylan/DeepGraft/training_tables/' + args.label_file
@@ -335,12 +375,21 @@ if __name__ == '__main__':
     Path(cfg.General.log_path).mkdir(exist_ok=True, parents=True)
     log_name =  f'_{cfg.Model.backbone}' + f'_{cfg.Loss.base_loss}'
     task = '_'.join(Path(cfg.config).name[:-5].split('_')[2:])
+    task = task.split('-')[0]
     cfg.task = task
     # task = Path(cfg.config).name[:-5].split('_')[2:][0]
     cfg.log_path = log_path / f'{cfg.Model.name}' / task / log_name 
     cfg.log_name = log_name
     print(cfg.task)
 
+    if cfg.Data.feature_extractor == 'retccl':
+        cfg.Model.in_features = 2048
+    elif cfg.Data.feature_extractor == 'histoencoder':
+        cfg.Model.in_features = 384
+    elif cfg.Data.feature_extractor == 'ctranspath':
+        cfg.Model.in_features = 784
+
+
 
     cfg.epoch = args.epoch
     
diff --git a/code/utils/__pycache__/utils.cpython-39.pyc b/code/utils/__pycache__/utils.cpython-39.pyc
index 1e7ec41fb93909621df30a4580ee96a07956c7d0..2e53ab71775243b1bf556dd504b74c58aca12855 100644
Binary files a/code/utils/__pycache__/utils.cpython-39.pyc and b/code/utils/__pycache__/utils.cpython-39.pyc differ
diff --git a/code/utils/export.sh b/code/utils/export.sh
new file mode 100644
index 0000000000000000000000000000000000000000..3de683cd68c65a86671d8aab13dac6361ac134d4
--- /dev/null
+++ b/code/utils/export.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+$model='TransMIL'
+$task='norm_rest'
+
+python export_metrics.py --model $model --task $task --target_label 0 
+python export_metrics.py --model $model --task $task --target_label 1 
+
+$task='rej_rest'
+
+python export_metrics.py --model $model --task $task --target_label 0 
+python export_metrics.py --model $model --task $task --target_label 1 
+
+$task='norm_rej_rest'
+
+python export_metrics.py --model $model --task $task --target_label 0 
+python export_metrics.py --model $model --task $task --target_label 1 
+python export_metrics.py --model $model --task $task --target_label 2 
\ No newline at end of file
diff --git a/code/utils/export_metrics.ipynb b/code/utils/export_metrics.ipynb
index be5a44e3917a0d3f89b158f9908ebb902c91fb23..ff9afba1f2483286a36e42ee2e96e740ecc553c5 100644
--- a/code/utils/export_metrics.ipynb
+++ b/code/utils/export_metrics.ipynb
@@ -1 +1 @@
-{"cells":[{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stderr","output_type":"stream","text":["/home/ylan/miniconda3/envs/pytorch2/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n","  from .autonotebook import tqdm as notebook_tqdm\n"]}],"source":["import argparse\n","from pathlib import Path\n","import numpy as np\n","from tqdm import tqdm\n","\n","import cv2\n","from PIL import Image, ImageFilter\n","from matplotlib import pyplot as plt\n","plt.style.use('tableau-colorblind10')\n","import pandas as pd\n","import json\n","import pprint\n","import seaborn as sns\n","import torch\n","\n","import torchmetrics\n","from torchmetrics import PrecisionRecallCurve, ROC\n","from torchmetrics.functional.classification import binary_auroc, multiclass_auroc, binary_precision_recall_curve, multiclass_precision_recall_curve, confusion_matrix\n","from torchmetrics.utilities.compute import _auc_compute_without_check, _auc_compute\n"]},{"cell_type":"code","execution_count":2,"metadata":{},"outputs":[{"data":{"text/plain":["'CLAM'"]},"execution_count":2,"metadata":{},"output_type":"execute_result"}],"source":["'''TransMIL'''\n","a = 'features'\n","add_on = '1'\n","\n","task = 'norm_rest'\n","model = 'TransMIL'\n","version = '804'\n","epoch = '30'\n","labels = ['Disease']\n","\n","\n","# task = 'rest_rej'\n","# model = 'TransMIL'\n","# version = '63'\n","# epoch = '14'\n","# labels = ['Rejection']\n","\n","# task = 'norm_rej_rest'\n","# model = 'TransMIL'\n","# version = '53'\n","# epoch = '17'\n","# labels = ['Normal', 'Rejection', 'Rest']\n","\n","'''ViT'''\n","# a = 'vit'\n","\n","# task = 'norm_rest'\n","# model = 'vit'\n","# version = '16'\n","# epoch = '142'\n","# labels = ['Disease']\n","\n","# task = 'rej_rest'\n","# model = 'vit'\n","# version = '1'\n","# epoch = 'last'\n","# labels = ['Rest']\n","\n","# task = 'norm_rej_rest'\n","# model = 'vit'\n","# version = '0'\n","# epoch = '226'\n","# labels = ['Normal', 'Rejection', 'Rest']\n","\n","'''CLAM'''\n","# task = 'norm_rest'\n","# model = 'CLAM'\n","# labels = ['REST']\n","\n","# task = 'rej_rest'\n","# model = 'CLAM'\n","# labels = ['REST']\n","\n","# task = 'norm_rej_rest'\n","# model = 'CLAM'\n","# labels = ['NORMAL', 'REJECTION', 'REST']\n","# labels = ['Normal', 'Rejection', 'Rest']\n","# if task == 'norm_rest' or task == 'rej_rest':\n","#     n_classes = 2\n","#     PRC = torchmetrics.PrecisionRecallCurve(task='binary')\n","#     ROC = torchmetrics.ROC(task='binary')\n","# else: \n","#     n_classes = 3\n","#     PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)\n","#     ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)\n","\n","\n"]},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["/home/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/TransMIL/norm_rest/_features_CrossEntropyLoss/lightning_logs/version_804/test_epoch_30\n","/home/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/TransMIL/norm_rest/_features_CrossEntropyLoss/lightning_logs/version_804/test_epoch_30/TEST_RESULT_PATIENT.csv\n","     Unnamed: 0        PATIENT  yTrue    Normal   Disease\n","0             0  KiBiAcREZZ331      1  0.135010  0.865234\n","1             1  KiBiAcVUFQ120      0  0.784180  0.215820\n","2             2  KiBiAcFZJQ730      1  0.203613  0.796387\n","3             3  KiBiAcNCMV110      1  0.269043  0.730957\n","4             4  KiBiAcTTVB560      1  0.050018  0.950195\n","..          ...            ...    ...       ...       ...\n","168         168  KiBiAcYEYR260      0  0.409424  0.590820\n","169         169  KiBiAcZHCX830      1  0.223022  0.776855\n","170         170  KiBiAcZKQY690      1  0.303223  0.696777\n","171         171  KiBiAcZRMP870      1  0.147217  0.852539\n","172         172  KiBiAcZUXX151      1  0.033478  0.966309\n","\n","[173 rows x 5 columns]\n","tensor([0.8652, 0.2158, 0.7964, 0.7310, 0.9502, 0.6284, 0.6396, 0.6494, 0.5610,\n","        0.8223, 0.7534, 0.2170, 0.8047, 0.5322, 0.6660, 0.9512, 0.8809, 0.5366,\n","        0.9487, 0.8501, 0.8931, 0.5732, 0.7661, 0.8789, 0.8867, 0.5664, 0.9419,\n","        0.7690, 0.8389, 0.5537, 0.9175, 0.8330, 0.6401, 0.4529, 0.3450, 0.7725,\n","        0.8604, 0.9009, 0.6860, 0.8071, 0.8955, 0.8418, 0.5566, 0.9165, 0.9072,\n","        0.5669, 0.6973, 0.7969, 0.9404, 0.5830, 0.8828, 0.6050, 0.8643, 0.5991,\n","        0.5107, 0.8843, 0.9639, 0.9136, 0.9575, 0.7964, 0.5371, 0.5127, 0.2839,\n","        0.9722, 0.8560, 0.7930, 0.6313, 0.6250, 0.8208, 0.9707, 0.8877, 0.5737,\n","        0.9189, 0.5918, 0.6445, 0.9292, 0.7485, 0.9453, 0.8984, 0.5264, 0.8525,\n","        0.7148, 0.7695, 0.7266, 0.9355, 0.9536, 0.9043, 0.5913, 0.8091, 0.9121,\n","        0.6616, 0.9229, 0.8818, 0.5410, 0.6880, 0.8232, 0.8877, 0.7949, 0.6836,\n","        0.8486, 0.8999, 0.3425, 0.9277, 0.5327, 0.6665, 0.5229, 0.7109, 0.7422,\n","        0.5415, 0.7129, 0.8208, 0.8740, 0.5420, 0.8276, 0.2900, 0.4573, 0.8745,\n","        0.9829, 0.3394, 0.6943, 0.9307, 0.9595, 0.9219, 0.8604, 0.7773, 0.6553,\n","        0.7822, 0.9375, 0.9512, 0.3113, 0.8115, 0.7344, 0.7295, 0.7451, 0.9536,\n","        0.2046, 0.6475, 0.6221, 0.8940, 0.7534, 0.2534, 0.8696, 0.7783, 0.2404,\n","        0.7168, 0.7974, 0.3701, 0.9277, 0.7983, 0.8467, 0.9614, 0.5732, 0.8555,\n","        0.6504, 0.2200, 0.9536, 0.7646, 0.5190, 0.8525, 0.8916, 0.7803, 0.7612,\n","        0.5498, 0.6406, 0.5581, 0.6895, 0.6699, 0.9590, 0.5908, 0.7769, 0.6968,\n","        0.8525, 0.9663], dtype=torch.float64)\n"]}],"source":["'''Find Directory'''\n","\n","home = Path.cwd().parts[1]\n","root_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/{model}/{task}/_{a}_CrossEntropyLoss/lightning_logs/version_{version}/test_epoch_{epoch}'\n","print(root_dir)\n","patient_result_csv_path = Path(root_dir) / 'TEST_RESULT_PATIENT.csv'\n","print(patient_result_csv_path)\n","threshold_csv_path = f'{root_dir}/val_thresholds.csv'\n","\n","# patient_result_csv_path = Path(f'/{home}/ylan/workspace/HIA/logs/DeepGraft_Lancet/clam_mb/DEEPGRAFT_CLAMMB_TRAINFULL_{task}/RESULTS/TEST_RESULT_PATIENT_BASED_FULL.csv')\n","# threshold_csv_path = ''\n","\n","output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'\n","Path(output_dir).mkdir(parents=True, exist_ok=True)\n","\n","patient_result = pd.read_csv(patient_result_csv_path)\n","pprint.pprint(patient_result)\n","\n","\n","\n","probs = torch.from_numpy(np.array(patient_result[labels]))\n","probs = probs.squeeze()\n","\n","    \n","# print(probs.shape)\n","\n","print(probs)\n","\n","\n","#     probs = \n","    \n","# probs = torch.transpose(probs, 0,1).squeeze()\n","target = torch.from_numpy(np.array(patient_result.yTrue))\n","\n","#swap values for rest_rej for it to align\n","if task == 'rest_rej':\n","    probs = 1-probs\n","    target = -1 * (target-1)\n","    task = 'rej_rest'\n","if add_on == '0':\n","    target = -1 * (target-1)\n","\n","# \n","# target = torch.stack((fake_target, target), dim=1)\n","# print(target.shae)\n","# print(target)\n","# target = -1 * (target-1)\n","# print(target)\n","\n"]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[],"source":["from utils import get_roc_curve, get_pr_curve, get_confusion_matrix"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"data":{"image/png":"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phMnTqi8vLzathaLRStXrqzxtmfPnq1nn31W6enp6tGjh/72t7+pX79+1bbPycnRlClTtHjxYp04cUJt27bVrFmzdM0119TqOwEAgMbBa6HlkUce0eOPPy632y3DMM7Z3mKx1HjbCxYs0IQJE/TSSy8pKSlJs2bNUmpqqnbv3q2oqKgz2peVlenXv/61oqKi9O677yo+Pl4HDhxQeHh4bb4SAABoRLwSWt5++2098sgjkqS4uDilpqYqLi5Ofn7eyUQvvPCCxowZo9GjR0uSXnrpJS1dulTz5s3TpEmTzmg/b948nThxQhs2bJC/v78kKTEx0Su1AAAA3/BKqpg9e7Yk6brrrtPChQtlt9u9sVlJp86abNmyRZMnT/Yss1qtSklJ0caNG6tc58MPP1RycrLGjh2rDz74QK1atdLw4cM1ceJE2Wy2KtcpLS1VaWmp531eXp7XvgMAAPjlvDIQd8eOHbJYLJozZ45XA4skZWdny+VyKTo6utLy6OhopaenV7nO3r179e6778rlcmnZsmV6+OGH9fzzz+vxxx+vtp+ZM2cqLCzM80pISPDq9wAAAL+MV0KLxWJRaGio4uLivLG5X8ztdisqKkqvvPKKevfurWHDhmnKlCl66aWXql1n8uTJys3N9bwOHTrUgBUDAIBz8crloc6dOystLU2lpaVyOBze2KRHZGSkbDabMjIyKi3PyMhQTExMlevExsbK39+/0qWgLl26KD09XWVlZVWeDXI4HF6vHQAAeI9XzrTceeedKi8v16JFi7yxuUrsdrt69+5d6fZot9utlStXKjk5ucp1BgwYoO+//15ut9uz7Ntvv1VsbKzXL18BAICG4ZXQMmbMGF133XW6//77tXbtWm9sspIJEybo1Vdf1VtvvaVvvvlG9957rwoLCz13E40cObLSQN17771XJ06c0B//+Ed9++23Wrp0qZ588kmNHTvW67UBAICG4ZXLQ48++qh69OihdevWadCgQRowYICSkpIUEhJy1vWmTZtWo+0PGzZMWVlZmjZtmtLT09WzZ08tX77cMzj34MGDslp/zF8JCQn6+OOPNX78eHXv3l3x8fH64x//qIkTJ9b9SwIAAJ+yGDWZCe4crFarZ7K405uryeRxLpfrl3Zdb/Ly8hQWFqbc3FyFhob6uhwAAEyjvo6hXjnTctlll9VqhlsAAIDa8kpo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atGihTp16uSt2ipJSkpS37599eKLL3r6TUhI0H333adJkyZVuY7L5dJll12m3//+91q3bp1ycnK0ZMmSGvXH5SEAAOqm0V0eKi8v18SJE9WiRQt169ZNAwYMUJcuXdSqVSs98cQTXp23paysTFu2bFFKSopnmdVqVUpKijZu3Fjteo8++qiioqJ0xx13nLOP0tJS5eXlVXoBAIDGo86Xh4YOHarly5efEU6OHz+uadOm6bvvvtObb775S+uTJGVnZ8vlcik6OrrS8ujoaO3atavKddavX6/XX39daWlpNepj5syZeuSRR35pqQAAoJ7U6UzLokWL9J///EeGYahjx46aPHmyZs+erQcffNAzO+4//vEPrVmzxtv11kh+fr5GjBihV199VZGRkTVaZ/LkycrNzfW8Dh06VM9VAgCA2qjTmZZ//vOfkqQrr7xSH3zwgRwOh+ezKVOm6IorrtDWrVv19ttv6/LLL//FRUZGRspmsykjI6PS8oyMDMXExJzRfs+ePdq/f7+GDBniWXb6biU/Pz/t3r1bHTp0qLSOw+Go9D0AAEDjUqczLV9++aUsFov+8pe/nHGgDw0N1dNPPy3DMLw2rb/dblfv3r21cuVKzzK3262VK1cqOTn5jPadO3fW9u3blZaW5nldd911GjRokNLS0pSQkOCVugAAQMOp05mW7OxsOZ1OdenSpcrP+/Tp42nnLRMmTNCoUaPUp08f9evXT7NmzVJhYaFGjx4tSRo5cqTi4+M1c+ZMOZ1Ode3atdL64eHhknTGcgAAYA51Ci2lpaVVXpY5LSwszNPOW4YNG6asrCxNmzZN6enp6tmzp5YvX+4ZnHvw4EFZrV6ZKw8AADRCdZqnxWq1KiYmRkePHv1FbRoz5mkBAKBuGt08LQAAAA2pzvO0ZGRknPVBiRaL5axtGvsDEwEAQONS59DizRlvAQAAzqVOoWX69OnergMAAOCsfvEDE5sqBuICAFA3DMQFAADNGqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYAqEFAACYgqlCy+zZs5WYmCin06mkpCRt2rSp2ravvvqqLr30UkVERCgiIkIpKSlnbQ8AABo304SWBQsWaMKECZo+fbq+/PJL9ejRQ6mpqcrMzKyy/erVq3Xrrbfqs88+08aNG5WQkKArr7xSR44caeDKAQCAN1gMwzB8XURNJCUlqW/fvnrxxRclSW63WwkJCbrvvvs0adKkc67vcrkUERGhF198USNHjjzj89LSUpWWlnre5+XlKSEhQbm5uQoNDfXeFwEAoInLy8tTWFiY14+hpjjTUlZWpi1btiglJcWzzGq1KiUlRRs3bqzRNoqKilReXq4WLVpU+fnMmTMVFhbmeSUkJHildgAA4B2mCC3Z2dlyuVyKjo6utDw6Olrp6ek12sbEiRMVFxdXKfj81OTJk5Wbm+t5HTp06BfXDQAAvMfP1wU0hKeeekrz58/X6tWr5XQ6q2zjcDjkcDgauDIAAFBTpggtkZGRstlsysjIqLQ8IyNDMTExZ133ueee01NPPaVPP/1U3bt3r88yAQBAPTLF5SG73a7evXtr5cqVnmVut1srV65UcnJytes988wzeuyxx7R8+XL16dOnIUoFAAD1xBRnWiRpwoQJGjVqlPr06aN+/fpp1qxZKiws1OjRoyVJI0eOVHx8vGbOnClJevrppzVt2jS98847SkxM9Ix9CQ4OVnBwsM++BwAAqBvThJZhw4YpKytL06ZNU3p6unr27Knly5d7BucePHhQVuuPJ47mzp2rsrIy/eY3v6m0nenTp2vGjBkNWToAAPAC08zT0tDq6x5z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2y3p1Bmhm266SdKpsSutW7fWokWLdPfdd0uSHn74Yc86vXr1UmFhoZ599tkzQsu6detUUFCgzz//XJMmTVLHjh1166231lv9tUVoAYAmKMBe8z/v9dX2XIKCgtSxY0fP+9dee01hYWF69dVXNXjwYN1222165JFHlJqaqrCwMM2fP1/PP/+8p/2MGTM0fPhwLV26VP/5z380ffp0zZ8/XzfccIMKCgp09913n3FQlqQ2bdrUuMbTl5ukHwPc6aBQkz5qc3koJiZGmzZtqvRZRkaG57OqnA5SF1xwgWeZw+FQ+/btzzqgOSkpSY899phKS0vlcDg8y9u1aydJ6tatmzIyMjRjxgxCCwAAP2exWGS1WlVcXKwNGzaobdu2mjJliufzAwcOnLFOp06d1KlTJ40fP1633nqr3njjDd1www266KKLtHPnzkqhyNtq0kdtLg8lJyfriSeeUGZmpufszIoVKxQaGloplPxU79695XA4tHv3bl1yySWSpPLycu3fv19t27attq+0tDRFRERUCiw/53a7VVpaWqPaGwqhBQDgE6WlpZ6xGidPntSLL76ogoICDRkyRHl5eTp48KDmz5+vvn37aunSpXr//fc96xYXF+vBBx/Ub37zG7Vr106HDx/WF1984blEMnHiRF188cUaN26c7rzzTgUFBWnnzp1asWKFXnzxRa/UX5M+anN56Morr9QFF1ygESNG6JlnnvHcBj527FhPuNi0aZNGjhyplStXKj4+XqGhobrnnns0ffp0JSQkqG3btnr22WclSTfffLMk6d///rcyMjJ08cUXy+l0asWKFXryyScrhanZs2erTZs2nvFBa9eu1XPPPVflWSRfIrQAAHxi+fLlnssbISEh6ty5sxYtWqSBAwdKksaPH69x48aptLRUgwcP1sMPP6wZM2ZIkmw2m44fP66RI0cqIyNDkZGRuvHGG/XII49IOjUWZc2aNZoyZYouvfRSGYahDh06aNiwYV6r39t92Gw2ffTRR7r33nuVnJysoKAgjRo1So8++qinTVFRkXbv3q3y8h/v4Hr22Wfl5+enESNGqLi4WElJSVq1apUiIiIknbrENXv2bI0fP16GYahjx4564YUXNGbMGM823G63Jk+erH379snPz08dOnTQ008/7RkT01hYDMMwfF1EY5SXl6ewsDDl5uZWGvgFAI1FSUmJ9u3bp3bt2tX4bhjAW872+1dfx1Cm8QcAAKZAaAEAAKZAaAEAAKZAaAEAAKZAaAEAk+N+CviCL37vCC0AYFKnZ2stKirycSVojsrKyiSdulW7oTBPCwCYlM1mU3h4uDIzMyVJgYGB1T61F/Amt9utrKwsBQYGys+v4aIEoQUATOz0M2lOBxegoVitVrVp06ZBgzKhBQBMzGKxKDY2VlFRUZVmSQXqm91ul9XasKNMCC0A0ATYbLYGHVsA+IKpBuLOnj1biYmJcjqdSkpKOuMR3j+3aNEide7cWU6nU926ddOyZcsaqFIAAOBtpgktCxYs0IQJEzR9+nR9+eWX6tGjh1JTU6u9jrthwwbdeuutuuOOO7R161YNHTpUQ4cO1Y4dOxq4cgAA4A2meWBiUlKS+vbt63nct9vtVkJCgu677z5NmjTpjPbDhg1TYWGhPvroI8+yiy++WD179tRLL710zv54YCIAAHVTX8dQU4xpKSsr05YtWzR58mTPMqvVqpSUFG3cuLHKdTZu3KgJEyZUWpaamqolS5ZU2b60tFSlpaWe97m5uZJO7XgAAFBzp4+d3j4vYorQkp2dLZfLpejo6ErLo6OjtWvXrirXSU9Pr7J9enp6le1nzpypRx555IzlCQkJdawaAIDm7fjx4woLC/Pa9kwRWhrC5MmTK52ZycnJUdu2bXXw4EGv7nBULy8vTwkJCTp06BCX5BoI+7zhsc8bHvu84eXm5qpNmzZq0aKFV7dritASGRkpm82mjIyMSsszMjI8Eyv9XExMTK3aOxwOORyOM5aHhYXxS97AQkND2ecNjH3e8NjnDY993vC8PY+LKe4estvt6t27t1auXOlZ5na7tXLlSiUnJ1e5TnJycqX2krRixYpq2wMAgMbNFGdaJGnChAkaNWqU+vTpo379+mnWrFkqLCzU6NGjJUkjR45UfHy8Zs6cKUn64x//qMsvv1zPP/+8Bg8erPnz52vz5s165ZVXfPk1AABAHZkmtAwbNkxZWVmaNm2a0tPT1bNnTy1fvtwz2PbgwYOVTkP1799f77zzjqZOnao///nPOu+887RkyRJ17dq1Rv05HA5Nnz69yktGqB/s84bHPm947POGxz5vePW1z00zTwsAAGjeTDGmBQAAgNACAABMgdACAABMgdACAABMoVmHltmzZysxMVFOp1NJSUnatGnTWdsvWrRInTt3ltPpVLdu3bRs2bIGqrTpqM0+f/XVV3XppZcqIiJCERERSklJOef/RjhTbX/PT5s/f74sFouGDh1avwU2QbXd5zk5ORo7dqxiY2PlcDjUqVMn/r7UUm33+axZs3T++ecrICBACQkJGj9+vEpKShqoWvNbu3athgwZori4OFkslmqf6/dTq1ev1kUXXSSHw6GOHTvqzTffrH3HRjM1f/58w263G/PmzTO+/vprY8yYMUZ4eLiRkZFRZfv//ve/hs1mM5555hlj586dxtSpUw1/f39j+/btDVy5edV2nw8fPtyYPXu2sXXrVuObb74xfve73xlhYWHG4cOHG7hy86rtPj9t3759Rnx8vHHppZca119/fcMU20TUdp+XlpYaffr0Ma655hpj/fr1xr59+4zVq1cbaWlpDVy5edV2n7/99tuGw+Ew3n77bWPfvn3Gxx9/bMTGxhrjx49v4MrNa9myZcaUKVOMxYsXG5KM999//6zt9+7dawQGBhoTJkwwdu7cafztb38zbDabsXz58lr122xDS79+/YyxY8d63rtcLiMuLs6YOXNmle1/+9vfGoMHD660LCkpybj77rvrtc6mpLb7/OcqKiqMkJAQ46233qqvEpucuuzziooKo3///sZrr71mjBo1itBSS7Xd53PnzjXat29vlJWVNVSJTU5t9/nYsWONK664otKyCRMmGAMGDKjXOpuqmoSWhx56yLjwwgsrLRs2bJiRmppaq76a5eWhsrIybdmyRSkpKZ5lVqtVKSkp2rhxY5XrbNy4sVJ7SUpNTa22PSqryz7/uaKiIpWXl3v9AVxNVV33+aOPPqqoqCjdcccdDVFmk1KXff7hhx8qOTlZY8eOVXR0tLp27aonn3xSLperoco2tbrs8/79+2vLli2eS0h79+7VsmXLdM011zRIzc2Rt46hppkR15uys7Plcrk8s+meFh0drV27dlW5Tnp6epXt09PT663OpqQu+/znJk6cqLi4uDN+8VG1uuzz9evX6/XXX1daWloDVNj01GWf7927V6tWrdJtt92mZcuW6fvvv9cf/vAHlZeXa/r06Q1RtqnVZZ8PHz5c2dnZuuSSS2QYhioqKnTPPffoz3/+c0OU3CxVdwzNy8tTcXGxAgICarSdZnmmBebz1FNPaf78+Xr//ffldDp9XU6TlJ+frxEjRujVV19VZGSkr8tpNtxut6KiovTKK6+od+/eGjZsmKZMmaKXXnrJ16U1WatXr9aTTz6pOXPm6Msvv9TixYu1dOlSPfbYY74uDefQLM+0REZGymazKSMjo9LyjIwMxcTEVLlOTExMrdqjsrrs89Oee+45PfXUU/r000/VvXv3+iyzSantPt+zZ4/279+vIUOGeJa53W5Jkp+fn3bv3q0OHTrUb9EmV5ff89jYWPn7+8tms3mWdenSRenp6SorK5Pdbq/Xms2uLvv84Ycf1ogRI3TnnXdKkrp166bCwkLdddddmjJlSqXn2ME7qjuGhoaG1vgsi9RMz7TY7Xb17t1bK1eu9Cxzu91auXKlkpOTq1wnOTm5UntJWrFiRbXtUVld9rkkPfPMM3rssce0fPly9enTpyFKbTJqu887d+6s7du3Ky0tzfO67rrrNGjQIKWlpSkhIaEhyzeluvyeDxgwQN9//70nIErSt99+q9jYWAJLDdRlnxcVFZ0RTE6HRoPH8dULrx1DazdGuOmYP3++4XA4jDfffNPYuXOncddddxnh4eFGenq6YRiGMWLECGPSpEme9v/9738NPz8/47nnnjO++eYbY/r06dzyXEu13edPPfWUYbfbjXfffdc4duyY55Wfn++rr2A6td3nP8fdQ7VX231+8OBBIyQkxBg3bpyxe/du46OPPjKioqKMxx9/3FdfwXRqu8+nT59uhISEGP/617+MvXv3Gp988onRoUMH47e//a2vvoLp5OfnG1u3bjW2bt1qSDJeeOEFY+vWrcaBAwcMwzCMSZMmGSNGjPC0P33L84MPPmh88803xuzZs7nlubb+9re/GW3atDHsdrvRr18/4/PPP/d8dvnllxujRo2q1H7hwoVGp06dDLvdblx44YXG0qVLG7hi86vNPm/btq0h6YzX9OnTG75wE6vt7/lPEVrqprb7fMOGDUZSUpLhcDiM9u3bG0888YRRUVHRwFWbW232eXl5uTFjxgyjQ4cOhtPpNBISEow//OEPxsmTJxu+cJP67LPPqvz7fHo/jxo1yrj88svPWKdnz56G3W432rdvb7zxxhu17tdiGJwLAwAAjV+zHNMCAADMh9ACAABMgdACAABMgdACAABMgdACAABMgdACAABMgdACAABMgdACAABMgdACAGdhsVhksVi0evXqWn0GwPsILQBqZcaMGZ6D9U9fDodDcXFxSk1N1Wuvvaby8nJflwqgiSG0AKiz6Ohoz8vPz0/Hjh3TJ598ojFjxqh///46efKkr0sE0IQQWgDUWXp6uudVWFioAwcOaMyYMZKkzZs36/777/dxhQCaEkILAK9p06aNXnnlFV1xxRWSpIULF6qgoMDHVQFoKggtALzuqquukiSVlZXpu+++O+Pz/Px8PfXUU0pOTlaLFi3kcDiUkJCgW265RRs3bjzn9j/55BPdcsstatu2rQICAtSiRQt1795d99133xnru91urVy5Uvfff78uvvhitW7dWna7XS1bttTll1+ul156ifE3gEn4+boAAE2PYRien10uV6XP0tLSNGTIEB0+fFiSZLPZFBgYqMOHD2vBggVauHChnnjiCU2ePPmM7RYVFel3v/udFi1a5FkWEhIit9ut7du3a/v27Vq3bp3S0tI8nx88eFApKSme98HBwQoMDNSJEye0du1arV27Vu+8844+/vhjBQQEeGsXAKgHnGkB4HUff/yxpFO3BLdr186z/NixY0pNTdXhw4d14403avPmzSouLlZeXp4yMjL08MMPy2az6c9//rOWLFlyxnZHjx6tRYsWyWq1auLEiTp06JDy8vKUk5OjrKwsvf3220pOTq60jp+fn2677TZ9+OGHOn78uPLz85WTk6P8/Hy98cYbiouL07p16zRlypR63ScAvMAAgFqYPn26Icmo6s/HgQMHjDFjxng+v+666yp9/vvf/96QZAwfPrza7b/wwguGJKNHjx6Vln/66aee7c6ZM8cr38UwDOOLL74wJBlBQUFGcXHxGZ+f7vOzzz6r1WcAvI8zLQDqLCYmxvMKCgpS27Zt9eqrr0qSOnfurDlz5njalpSU6J133pEkTZw4sdptjhw5UpL01VdfKSMjw7N83rx5kqSuXbvq3nvv9dp36NOnj6KiolRYWFjpshKAxocxLQDq7Keh4qdGjhypl19+WU6n07Nsy5YtKikpkSRdeeWVNdr+gQMHFB0dLUnasGGDJOnaa6+tdZ1lZWWaN2+eFi9erB07duj48eMqKys7o93pcTYAGidCC4A6M34YcGsYhtLT0/Xhhx9q0qRJ+vvf/65u3brpgQce8LQ9evSo5+fqws7PFRUVeX5OT0+XJLVt27ZWNWZmZiolJUXbt2/3LHM6nYqMjJTNZpMkZWVlye12q7CwsFbbBtCwuDwE4BezWCyKjY3V3Xffrffff18Wi0UPPfSQVq1a5Wnz07uIiouLZRjGOV8DBw6s1EddjB8/Xtu3b1fLli01b948HTt2TMXFxcrKyvJMjBcXFyep8l1PABofQgsArxo4cKBGjBghwzB03333ecJKTEyMp82BAwdqvd3T69dm3fLyci1evFiS9OKLL2r06NGV6pBOhans7Oxa1wOg4RFaAHjdtGnTZLPZtHPnTr311luSpL59+8put0uS/v3vf9d6m/3796/1ullZWZ5xNL169aqyzfr16z1tADRuhBYAXtehQwcNGzZMkvTYY4+pvLxcQUFBGj58uCTp6aef1sGDB8+6jRMnTlR6f8cdd0iSvv76a82dO7dGdYSGhnouK3311VdnfF5RUcH8LICJEFoA1IvJkyfLYrFo//79ev311yVJTz75pOLi4pSdna3k5GT94x//UH5+vmedrKwsvffee7rhhht06623VtreoEGDdMstt0iSxo0bp8mTJ1e62yc7O1uvvfaaJ9xIp2a/HTBggCRpwoQJWrVqldxutyRpx44duuaaa7R582YFBQXVz04A4F0+mR0GgGmdbXK5n7v++usNSUbr1q2NkpISwzAMY+fOnUanTp0827BarUaLFi2MoKAgzzJJRkpKyhnbKywsNG688cZK7UJDQ42wsDDP+59PSrd58+ZK23Y4HEZISIghyfDz8zP+/ve/G23btjUkGW+88cYZfYrJ5YBGgzMtAOrN6Usvhw8f1ssvvyxJ6tKli7Zt26aXX35ZV155pSIjI5WXlyfDMNSxY0fdfPPNeuWVV7Rw4cIzthcYGKj33ntPH330kW644QbFxcWppKREfn5+6t69u+6//3698sorldbp3bu3Nm3apN/+9reKjIyU2+1WSEiIfvvb32rDhg0aMWJE/e8IAF5hMQzu8QMAAI0fZ1oAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIApEFoAAIAp/H9j8jvypeJdGgAAAABJRU5ErkJggg==","text/plain":["<Figure size 600x600 with 1 Axes>"]},"metadata":{},"output_type":"display_data"},{"data":{"image/png":"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NJmYK7eHvJXVCrfeCj/8oAaW776TwCKEcCuZvyL+TuvfExJavJHVCrfcAj/+CCaTGliuuUbrqoQQQogaJaHFGxUXQ0qKGli+/x4GDNC6IiGEEKLGSWjxRuHhsHIl7NsHV16pdTVCCCFErZDVQ96ipES9DVQmMlICixBC1LLi4mJeeOEFWrZsiclkIiEhgTFjxpT3L6mKlStXMnjwYOrVq0dAQABRUVFcc801LFq06Lzn9+7dG51Od8Gv5cuXn/Ocl1566R+f8/TTT1e5bi3JSIs3KClRNztcvhw+/BDGjtW6IiGEqHNKSkro27cvW7duJT4+nhtuuIHjx4/z2Wef8eOPP7J161aaNm1aqdeaMmUKjz/+ODqdju7du9OwYUOSk5P5+eefWblyJc888wyvvfbaeZ978803ExIScs7x+vXrX/B6PXv2pPl5enh16tSpUvV6Cgktnq64WN3s8KefICgILrlE64qEEKJOevXVV9m6dSvdu3fnp59+Kg8O77zzDhMmTGDMmDGsXbv2oq+TkZHB008/TUBAACtXrqRXr17lj61fv55rrrmGyZMnc88995w3BL311ls0bty4SrXfe++93HXXXVV6jieS20OerLhY3eywLLAsXQq9e2tdlRBC1DmlpaV88MEHAEydOrXCSMf48eNp164d69atY9euXRd9rW3btmG1Wunbt2+FwAJw9dVXM3DgQBRFYefOne79RfgACS2eqqgIrr9enXAbHAzLlsHffnMLIYSoHZs2bSIvL49mzZpx+eWXn/P4LbfcAsAPP/xw0deqbNO+qKioqhVZB8jtIU9kt6uBZdWqs4Hlqqu0rkoIIeqsX3/9FYCOHTue9/Gy47/99ttFX6tr166Eh4ezevVq1q1bd87toRUrVtCiRQuuusDf+zNmzCArKws/Pz9atmzJ0KFDadSo0T9ec/Xq1ezZs4eSkhIaNGjAoEGDvG4+C0ho8Uz+/uqoyrZtamCRVUJCCA+iKAolNofWZVSaKaD6u1MnJSUB0KBBg/M+Xnb8xIkTF30ts9nMjBkzGDFiBH369KFHjx40aNCAkydPsnnzZnr27MmcOXMwGAznff6rr75a4ecnnniC559/nueff/6C15w7d26Fn59//nluvvlmZs2add5JvZ5KQounev55uOsuaNhQ60qEEKKCEpuDPq+t0rqMSlvzbD8CDdX7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","text/plain":["<Figure size 600x600 with 1 Axes>"]},"metadata":{},"output_type":"display_data"}],"source":["# from utils import get_roc_curve, get_pr_curve, get_confusion_matrix\n","\n","stage='test'\n","comment='patient'\n","pr_plot = get_pr_curve(probs, target, task=task)\n","pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.png', dpi=400)\n","pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.svg', format='svg')\n","plt.show()\n","\n","pr_plot.figure.clf()\n","\n","roc_plot = get_roc_curve(probs, target, task=task)\n","roc_plot.legend(loc='lower right', fontsize=15)\n","roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.png', dpi=400)\n","roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.svg', format='svg')\n","plt.show()\n","\n","roc_plot.figure.clf()"]},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Optimal Threshold test patient:  0.7783203125\n"]},{"data":{"image/png":"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314ff/yxbrjhhvLOCAAA4FLmNS/Dhw9XQUGBkpKSlJ6ervT0dCUlJcnpdGr48OEVkREAAMClzDMvX375pbZt26aoqCjXWFRUlN555x117NixXMMBAABcrswzL/Xq1SvxZnRFRUWqU6dOuYQCAAC4kjKXlzfeeENPPfWUdu7c6RrbuXOnRo8erTfffLNcwwEAAFzOYRiGca2dqlatKofD4Xp97tw5FRYWysvrwlmni/9dpUoVpaenV1zaUuo4dYvZEQBUkLWjbzc7AoAK4lfKxSyl2u1vf/vb74gCAABQfkpVXmJjYys6BwAAQKn85pvUSVJubq7y8/PdxoKDg39XIAAAgKsp84Ldc+fO6cknn1StWrVUpUoVVa1a1e0LAACgIpW5vDz33HNav3693nvvPfn6+uqDDz7QxIkTVadOHc2ePbsiMgIAALiU+bTR8uXLNXv2bN1xxx16+OGH1bFjR0VGRioiIkJz587V4MGDKyInAACApN8w85Kenq5GjRpJurC+5eKl0bfffrs2bdpUvukAAAAuU+by0qhRIx09elSSdOONN2rBggWSLszIXHxQIwAAQEUpc3l5+OGHtW/fPknSuHHj9I9//EN+fn4aM2aMnn322XIPCAAAcKlS3WH3av7zn/9o165dioyM1M0331xeuX4X7rAL/HFxh13gj6tc77B7NREREYqIiPi9hwEAACiVUpWXadOmlfqAo0aN+s1hAAAArqVUp40aNmxYuoM5HDpy5MjvDvV7Td+eYnYEABVkzEieXg/8UZ3f826p9ivVzMvFq4sAAADMVuarjQAAAMxEeQEAALZCeQEAALZCeQEAALZCeQEAALbym8rL5s2bNWTIELVr107Hjx+XJM2ZM0dbtnBnWwAAULHKXF4WLVqkHj16yN/fX3v27FFeXp4kKTMzU6+++mq5BwQAALhUmcvLyy+/rOnTp+v999+Xt7e3a7xDhw7avXt3uYYDAAC4XJnLS3Jysjp16lRsPCQkRBkZGeWRCQAA4IrKXF7Cw8N16NChYuNbtmxRo0aNyiUUAADAlZS5vIwYMUKjR4/Wjh075HA4dOLECc2dO1dxcXF64oknKiIjAACAS6mebXSpcePGyel06q677lJOTo46deokX19fxcXF6amnnqqIjAAAAC6leqp0SfLz83Xo0CFlZ2erWbNmCgwMLO9svxlPlQb+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AAAsyNSZl8TERG3atEnr1q1Tly5d3LatX79effv21ezZszV06FCTEgIAAKsxdeZl3rx5ev7554sVF0m68847NW7cOM2dO9eEZAAAwKpMLS/ffvutevbsecXtd999N0+VBgAAbkwtL+np6QoLC7vi9rCwMJ05c6YSEwEAAKsztbwUFRXJy+vKy248PT1VWFhYiYkAAIDVmbpg1zAMDRs2TL6+viVuz8vLq+REAADA6kwtL7GxsdfchyuNAADApUwtLzNnzjTz4wEAgA2ZuuYFAACgrCgvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVhyGYRhmhwB+q7y8PCUkJGj8+PHy9fU1Ow6AcsTPN66E8gJby8rKUkhIiDIzMxUcHGx2HADliJ9vXAmnjQAAgK1QXgAAgK1QXgAAgK1QXmBrvr6+eumll1jMB/wB8fONK2HBLgAAsBVmXgAAgK1QXgAAgK1QXgAAgK1QXoASbNy4UQ6HQxkZGWZHAWzN4XBo6dKlZsfAHwzlBRVu2LBhcjgceu2119zGly5dKofDYVIqAL/HxZ9rh8Mhb29vhYWFqVu3bvroo4/kdDpd+6Wlpenuu+82MSn+iCgvqBR+fn56/fXXdebMmXI7Zn5+frkdC0DZ9ezZU2lpaUpJSdHq1avVpUsXjR49Wvfee68KCwslSeHh4VzqjHJHeUGl6Nq1q8LDw5WQkHDFfRYtWqTmzZvL19dXDRo00NSpU922N2jQQJMnT9bQoUMVHBys//qv/1JiYqJCQ0O1YsUKRUVFKSAgQAMHDlROTo5mzZqlBg0aqGrVqho1apSKiopcx5ozZ47atm2roKAghYeH68EHH9TPP/9cYd8/8Efk6+ur8PBw1a1bVzExMXr++ef16aefavXq1UpMTJTkftooPz9fTz75pGrXri0/Pz9FRES4/U7IyMjQ8OHDVbNmTQUHB+vOO+/Uvn37XNsPHz6sPn36KCwsTIGBgbrlllv0xRdfuGX65z//qSZNmsjPz09hYWEaOHCga5vT6VRCQoIaNmwof39/tWzZUgsXLqy4PyBUGMoLKoWnp6deffVVvfPOOzp27Fix7bt27dKgQYP0l7/8Rfv379eECRMUHx/v+gV40ZtvvqmWLVtqz549io+PlyTl5ORo2rRp+vjjj7VmzRpt3LhR/fr106pVq7Rq1SrNmTNHM2bMcPslVVBQoMmTJ2vfvn1aunSpUlJSNGzYsIr8IwCuC3feeadatmypxYsXF9s2bdo0LVu2TAsWLFBycrLmzp2rBg0auLb/+c9/1s8//6zVq1dr165diomJ0V133aX09HRJUnZ2tnr16qV169Zpz5496tmzp+677z6lpqZKknbu3KlRo0Zp0qRJSk5O1po1a9SpUyfX8RMSEjR79mxNnz5d33//vcaMGaMhQ4boyy+/rNg/FJQ/A6hgsbGxRp8+fQzDMIzbbrvNeOSRRwzDMIwlS5YYF/8KPvjgg0a3bt3c3vfss88azZo1c72OiIgw+vbt67bPzJkzDUnGoUOHXGOPPfaYERAQYJw9e9Y11qNHD+Oxxx67YsZvvvnGkOR6z4YNGwxJxpkzZ8r+DQPXgUt/ri93//33G9HR0YZhGIYkY8mSJYZhGMZTTz1l3HnnnYbT6Sz2ns2bNxvBwcFGbm6u23jjxo2NGTNmXDFH8+bNjXfeeccwDMNYtGiRERwcbGRlZRXbLzc31wgICDC2bdvmNv7oo48aDzzwwBWPD2ti5gWV6vXXX9esWbOUlJTkNp6UlKQOHTq4jXXo0EEHDx50O93Ttm3bYscMCAhQ48aNXa/DwsLUoEEDBQYGuo1delpo165duu+++1S/fn0FBQWpc+fOkuT6FxyA384wjBIX4w8bNkx79+5VVFSURo0apc8//9y1bd++fcrOzlb16tUVGBjo+jp69KgOHz4s6cLMS1xcnKKjoxUaGqrAwEAlJSW5fm67deumiIgINWrUSA899JDmzp2rnJwcSdKhQ4eUk5Ojbt26uR1/9uzZruPDPrzMDoDrS6dOndSjRw+NHz/+N52mqVKlSrExb29vt9cXr364fOziFRDnzp1Tjx491KNHD82dO1c1a9ZUamqqevTowSJgoBwkJSWpYcOGxcZjYmJ09OhRrV69Wl988YUGDRqkrl27auHChcrOzlbt2rW1cePGYu8LDQ2VJMXFxWnt2rV68803FRkZKX9/fw0cOND1cxsUFKTdu3dr48aN+vzzz/Xiiy9qwoQJ+uabb5SdnS1JWrlyperWret2fBYU2w/lBZXutddeU6tWrRQVFeUai46O1tatW93227p1q5o2bSpPT89y/fwDBw7o119/1WuvvaZ69epJunCuHMDvt379eu3fv19jxowpcXtwcLDuv/9+3X///Ro4cKB69uyp9PR0xcTE6OTJk/Ly8nJbB3OprVu3atiwYerXr5+kCzMxKSkpbvt4eXmpa9eu6tq1q1566SWFhoZq/fr16tatm3x9fZWamuqaaYV9UV5Q6Vq0aKHBgwdr2rRprrFnnnlGt9xyiyZPnqz7779f27dv17vvvqt//vOf5f759evXl4+Pj9555x09/vjj+u677zR58uRy/xzgjy4vL08nT55UUVGRTp06pTVr1ighIUH33nuvhg4dWmz/t956S7Vr11br1q3l4eGhTz75ROHh4QoNDVXXrl3Vrl079e3bV1OmTFHTpk114sQJrVy5Uv369VPbtm3VpEkTLV68WPfdd58cDofi4+Pd7imzYsUKHTlyRJ06dVLVqlW1atUqOZ1ORUVFKSgoSHFxcRozZoycTqduv/12ZWZmauvWrQoODlZsbGxl/tHhd6K8wBSTJk3S/PnzXa9jYmK0YMECvfjii5o8ebJq166tSZMmVcgVQDVr1lRiYqKef/55TZs2TTExMXrzzTfVu3fvcv8s4I9szZo1ql27try8vFS1alW1bNlS06ZNU2xsrDw8ii+pDAoK0pQpU3Tw4EF5enrqlltu0apVq1z7rlq1Si+88IIefvhhnT59WuHh4erUqZPCwsIkXSg/jzzyiNq3b68aNWpo7NixysrKch0/NDRUixcv1oQJE5Sbm6smTZpo3rx5at68uSRp8uTJqlmzphISEnTkyBGFhoa6LvGG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","text/plain":["<Figure size 640x480 with 1 Axes>"]},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":["<Figure size 640x480 with 0 Axes>"]},"metadata":{},"output_type":"display_data"}],"source":["stage = 'test'\n","comment='patient'\n","\n","cm_plot = get_confusion_matrix(probs, target, task=task, threshold_csv_path = threshold_csv_path)\n","\n","plt.show()\n","# cm_plot.figure.set(font_scale=18)\n","cm_plot.savefig(f'{output_dir}/{task}_cm.png', dpi=400)\n","cm_plot.savefig(f'{output_dir}/{task}_cm.svg', format='svg')\n","\n","# plt.savefig(f'{output_dir}/{task}_cm.png', dpi=400)\n","\n","cm_plot.clf()\n","\n","\n","\n"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[],"source":["# print((Path(output_dir)/task).stem)\n","# print(output_dir)\n","# model = 'vit'\n","# task = 'norm_rest'\n","# output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'\n","# out_dir = Path(output_dir)/task\n","# for i in Path(out_dir).iterdir():\n","#     if i.is_file():\n","#         name = i.name.rsplit('_', 1)[1]\n","#         # print(i)\n","#         # print(i.parents[0])\n","#         new_name = Path(output_dir) / f'{task}_{name}'\n","#         print(new_name)\n","#         i.rename(new_name)\n","    # print(new_name)\n"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[{"ename":"NameError","evalue":"name 'patient_score' is not defined","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)","\u001b[1;32m/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb Cell 8\u001b[0m in \u001b[0;36m<cell line: 3>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=0'>1</a>\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mscipy\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mstats\u001b[39;00m \u001b[39mimport\u001b[39;00m bootstrap\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=1'>2</a>\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39msklearn\u001b[39;00m\n\u001b[0;32m----> <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=2'>3</a>\u001b[0m res \u001b[39m=\u001b[39m bootstrap((patient_score\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy(), patient_target\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy()), sklearn\u001b[39m.\u001b[39mmetrics\u001b[39m.\u001b[39mroc_auc_score, confidence_level\u001b[39m=\u001b[39m\u001b[39m0.95\u001b[39m, paired\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m, vectorized\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m)\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a>\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m'\u001b[39m\u001b[39mbootstrap AUC: \u001b[39m\u001b[39m'\u001b[39m, res)\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a>\u001b[0m patient_AUC \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mAUROC(patient_score, patient_target\u001b[39m.\u001b[39msqueeze())\n","\u001b[0;31mNameError\u001b[0m: name 'patient_score' is not defined"]}],"source":["from scipy.stats import bootstrap\n","import sklearn\n","res = bootstrap((patient_score.cpu().numpy(), patient_target.cpu().numpy()), sklearn.metrics.roc_auc_score, confidence_level=0.95, paired=True, vectorized=False)\n","\n","print('bootstrap AUC: ', res)\n","patient_AUC = self.AUROC(patient_score, patient_target.squeeze())"]}],"metadata":{"kernelspec":{"display_name":"Python 3 (ipykernel)","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.16"},"orig_nbformat":4,"vscode":{"interpreter":{"hash":"e036a9c91377bd599a54855c8808043bcb982545d7d4bb9989918e49d09c4e97"}}},"nbformat":4,"nbformat_minor":2}
+{"cells":[{"cell_type":"code","execution_count":2,"metadata":{},"outputs":[{"name":"stderr","output_type":"stream","text":["/home/ylan/miniconda3/envs/pytorch2/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n","  from .autonotebook import tqdm as notebook_tqdm\n"]}],"source":["import argparse\n","from pathlib import Path\n","import numpy as np\n","from tqdm import tqdm\n","\n","import cv2\n","from PIL import Image, ImageFilter\n","from matplotlib import pyplot as plt\n","plt.style.use('tableau-colorblind10')\n","import pandas as pd\n","import json\n","import pprint\n","import seaborn as sns\n","import torch\n","\n","import torchmetrics\n","from torchmetrics import PrecisionRecallCurve, ROC\n","from torchmetrics.functional.classification import binary_auroc, multiclass_auroc, binary_precision_recall_curve, multiclass_precision_recall_curve, confusion_matrix\n","from torchmetrics.utilities.compute import _auc_compute_without_check, _auc_compute\n"]},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[{"data":{"text/plain":["'CLAM'"]},"execution_count":3,"metadata":{},"output_type":"execute_result"}],"source":["'''TransMIL'''\n","a = 'features'\n","add_on = '1'\n","\n","task = 'norm_rest'\n","model = 'TransMIL'\n","version = '804'\n","epoch = '30'\n","labels = ['Disease']\n","\n","\n","# task = 'rest_rej'\n","# model = 'TransMIL'\n","# version = '63'\n","# epoch = '14'\n","# labels = ['Rejection']\n","\n","# task = 'norm_rej_rest'\n","# model = 'TransMIL'\n","# version = '53'\n","# epoch = '17'\n","# labels = ['Normal', 'Rejection', 'Rest']\n","\n","'''ViT'''\n","# a = 'vit'\n","\n","# task = 'norm_rest'\n","# model = 'vit'\n","# version = '16'\n","# epoch = '142'\n","# labels = ['Disease']\n","\n","# task = 'rej_rest'\n","# model = 'vit'\n","# version = '1'\n","# epoch = 'last'\n","# labels = ['Rest']\n","\n","# task = 'norm_rej_rest'\n","# model = 'vit'\n","# version = '0'\n","# epoch = '226'\n","# labels = ['Normal', 'Rejection', 'Rest']\n","\n","'''CLAM'''\n","# task = 'norm_rest'\n","# model = 'CLAM'\n","# labels = ['REST']\n","\n","# task = 'rej_rest'\n","# model = 'CLAM'\n","# labels = ['REST']\n","\n","# task = 'norm_rej_rest'\n","# model = 'CLAM'\n","# labels = ['NORMAL', 'REJECTION', 'REST']\n","# labels = ['Normal', 'Rejection', 'Rest']\n","# if task == 'norm_rest' or task == 'rej_rest':\n","#     n_classes = 2\n","#     PRC = torchmetrics.PrecisionRecallCurve(task='binary')\n","#     ROC = torchmetrics.ROC(task='binary')\n","# else: \n","#     n_classes = 3\n","#     PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)\n","#     ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)\n","\n","\n"]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["/home/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/TransMIL/norm_rest/_features_CrossEntropyLoss/lightning_logs/version_804/test_epoch_30\n","/home/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/TransMIL/norm_rest/_features_CrossEntropyLoss/lightning_logs/version_804/test_epoch_30/TEST_RESULT_PATIENT.csv\n","     Unnamed: 0        PATIENT  yTrue    Normal   Disease\n","0             0  KiBiAcREZZ331      1  0.135010  0.865234\n","1             1  KiBiAcVUFQ120      0  0.784180  0.215820\n","2             2  KiBiAcFZJQ730      1  0.203613  0.796387\n","3             3  KiBiAcNCMV110      1  0.269043  0.730957\n","4             4  KiBiAcTTVB560      1  0.050018  0.950195\n","..          ...            ...    ...       ...       ...\n","168         168  KiBiAcYEYR260      0  0.409424  0.590820\n","169         169  KiBiAcZHCX830      1  0.223022  0.776855\n","170         170  KiBiAcZKQY690      1  0.303223  0.696777\n","171         171  KiBiAcZRMP870      1  0.147217  0.852539\n","172         172  KiBiAcZUXX151      1  0.033478  0.966309\n","\n","[173 rows x 5 columns]\n","tensor([0.8652, 0.2158, 0.7964, 0.7310, 0.9502, 0.6284, 0.6396, 0.6494, 0.5610,\n","        0.8223, 0.7534, 0.2170, 0.8047, 0.5322, 0.6660, 0.9512, 0.8809, 0.5366,\n","        0.9487, 0.8501, 0.8931, 0.5732, 0.7661, 0.8789, 0.8867, 0.5664, 0.9419,\n","        0.7690, 0.8389, 0.5537, 0.9175, 0.8330, 0.6401, 0.4529, 0.3450, 0.7725,\n","        0.8604, 0.9009, 0.6860, 0.8071, 0.8955, 0.8418, 0.5566, 0.9165, 0.9072,\n","        0.5669, 0.6973, 0.7969, 0.9404, 0.5830, 0.8828, 0.6050, 0.8643, 0.5991,\n","        0.5107, 0.8843, 0.9639, 0.9136, 0.9575, 0.7964, 0.5371, 0.5127, 0.2839,\n","        0.9722, 0.8560, 0.7930, 0.6313, 0.6250, 0.8208, 0.9707, 0.8877, 0.5737,\n","        0.9189, 0.5918, 0.6445, 0.9292, 0.7485, 0.9453, 0.8984, 0.5264, 0.8525,\n","        0.7148, 0.7695, 0.7266, 0.9355, 0.9536, 0.9043, 0.5913, 0.8091, 0.9121,\n","        0.6616, 0.9229, 0.8818, 0.5410, 0.6880, 0.8232, 0.8877, 0.7949, 0.6836,\n","        0.8486, 0.8999, 0.3425, 0.9277, 0.5327, 0.6665, 0.5229, 0.7109, 0.7422,\n","        0.5415, 0.7129, 0.8208, 0.8740, 0.5420, 0.8276, 0.2900, 0.4573, 0.8745,\n","        0.9829, 0.3394, 0.6943, 0.9307, 0.9595, 0.9219, 0.8604, 0.7773, 0.6553,\n","        0.7822, 0.9375, 0.9512, 0.3113, 0.8115, 0.7344, 0.7295, 0.7451, 0.9536,\n","        0.2046, 0.6475, 0.6221, 0.8940, 0.7534, 0.2534, 0.8696, 0.7783, 0.2404,\n","        0.7168, 0.7974, 0.3701, 0.9277, 0.7983, 0.8467, 0.9614, 0.5732, 0.8555,\n","        0.6504, 0.2200, 0.9536, 0.7646, 0.5190, 0.8525, 0.8916, 0.7803, 0.7612,\n","        0.5498, 0.6406, 0.5581, 0.6895, 0.6699, 0.9590, 0.5908, 0.7769, 0.6968,\n","        0.8525, 0.9663], dtype=torch.float64)\n"]}],"source":["'''Find Directory'''\n","\n","home = Path.cwd().parts[1]\n","root_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/{model}/{task}/_{a}_CrossEntropyLoss/lightning_logs/version_{version}/test_epoch_{epoch}'\n","print(root_dir)\n","patient_result_csv_path = Path(root_dir) / 'TEST_RESULT_PATIENT.csv'\n","print(patient_result_csv_path)\n","threshold_csv_path = f'{root_dir}/val_thresholds.csv'\n","\n","# patient_result_csv_path = Path(f'/{home}/ylan/workspace/HIA/logs/DeepGraft_Lancet/clam_mb/DEEPGRAFT_CLAMMB_TRAINFULL_{task}/RESULTS/TEST_RESULT_PATIENT_BASED_FULL.csv')\n","# threshold_csv_path = ''\n","\n","output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'\n","Path(output_dir).mkdir(parents=True, exist_ok=True)\n","\n","patient_result = pd.read_csv(patient_result_csv_path)\n","pprint.pprint(patient_result)\n","\n","probs = torch.from_numpy(np.array(patient_result[labels]))\n","probs = probs.squeeze()\n","\n","print(probs)\n","\n","\n","#     probs = \n","    \n","# probs = torch.transpose(probs, 0,1).squeeze()\n","target = torch.from_numpy(np.array(patient_result.yTrue))\n","\n","#swap values for rest_rej for it to align\n","if task == 'rest_rej':\n","    probs = 1-probs\n","    target = -1 * (target-1)\n","    task = 'rej_rest'\n","if add_on == '0':\n","    target = -1 * (target-1)\n","\n","# \n","# target = torch.stack((fake_target, target), dim=1)\n","# print(target.shae)\n","# print(target)\n","# target = -1 * (target-1)\n","# print(target)\n","\n"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[],"source":["from utils import get_roc_curve, get_pr_curve, get_confusion_matrix"]},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[{"data":{"image/png":"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2z8rKwvTp0/HunXrkJmZiUaNGmHu3LkYOHBgpT4TERER1Q4OCy3vvPMO3nvvPVitVgghbtlfkqQKb3vVqlWIjo7GokWL0KNHD8ydOxcDBgzAuXPn4O/vX6a/yWTCPffcA39/f6xduxYhISGIj4+Hl5dXZT4SERER1SIOCS3ff/893nnnHQBAcHAwBgwYgODgYLi4OCYTffbZZ5g0aRImTJgAAFi0aBE2bdqEpUuXYurUqWX6L126FJmZmThw4ABUKhUAoHHjxg6phYiIiJzDIali/vz5AIAhQ4Zg9erVUKvVjtgsgOKzJocPH8a0adNsbQqFAlFRUYiJibG7zsaNGxEZGYnJkyfjxx9/hJ+fH0aPHo0pU6ZAqVTaXcdoNMJoNNreZ2dnO+wzEBER0e1zyEDckydPQpIkLFiwwKGBBQDS09NhsVgQEBBQqj0gIADJycl217l06RLWrl0Li8WCzZs346233sKnn36K9957r9z9zJkzB3q93vYKCwtz6OcgIiKi2+OQ0CJJEjw9PREcHOyIzd02q9UKf39/LF68GF26dMHIkSMxffp0LFq0qNx1pk2bBoPBYHslJCTUYMVERER0Kw65PBQeHo7Y2FgYjUZoNBpHbNLG19cXSqUSKSkppdpTUlIQGBhod52goCCoVKpSl4Jat26N5ORkmEwmu2eDNBqNw2snIiIix3HImZYnnngCZrMZa9asccTmSlGr1ejSpUup26OtVit27NiByMhIu+v06tULf/75J6xWq63t/PnzCAoKcvjlKyIiIqoZDgktkyZNwpAhQ/DCCy9g7969jthkKdHR0fjqq6/w7bff4syZM3jmmWeQl5dnu5to7NixpQbqPvPMM8jMzMSLL76I8+fPY9OmTXj//fcxefJkh9dGRERENcMhl4feffddREREYN++fejXrx969eqFHj16wMPD46brzZgxo0LbHzlyJNLS0jBjxgwkJyejY8eO2LJli21w7pUrV6BQ/JW/wsLCsHXrVrz88svo0KEDQkJC8OKLL2LKlClV/5BERETkVJKoyExwt6BQKGyTxZVsriKTx1ksltvddbXJzs6GXq+HwWCAp6ens8shIiKSjer6DnXImZa77rqrUjPcEhEREVWWQ0LL7t27HbEZIiIionLxKc9EREQkCwwtREREJAsOe8pzCZPJhG3btuHQoUNITU0FAPj7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NbIJLXUFQwsREVHVVNd3KJ/yTERERLLgkMnl/i49PR27du1CfHw88vPzMWPGDEfvgoiIiOohh10eKioqwpQpU7BgwQKYTCZbu8Visf18/fp1NG3aFAUFBTh79iwaN27siF1XC14eIiIiqppaf3loxIgRmDt3LkwmE9q2bQsXl7Incby9vTF69GiYTCY+5ZmIiIgqxSGhZeXKlfjxxx/h7++PQ4cO4fjx4/Dx8bHbd8SIEQCAXbt2OWLXREREVE84JLR88803kCQJH3/8MTp16nTTvt27d4ckSTh9+rQjdk1ERET1hENCy9GjRwEAw4cPv2VfV1dX6PV6pKamOmLXREREVE84JLQYDAbo9XrodLoK9bdarbbZcomIiIgqwiGhxdvbGwaDAYWFhbfse+3aNWRnZyMgIMARuyYiIqJ6wiGhpXPnzgAqNrh26dKlAIDIyEhH7JqIiIjqCYeElkcffRRCCLz11lvIzc0tt9+WLVswa9YsSJKEcePGOWLXREREVE84ZEbc0aNHY/Hixdi3bx/uuOMOPP3007YJ5rZt24bLly/jp59+wubNm2G1WjF48GAMGDDAEbsmIiKiesJhM+Jev34dDzzwAPbu3VvuIFshBKKiorBu3Tq4u7s7YrfVhjPiEhERVU2tnxHX29sbO3fuxLfffos777wTarUaQggIIaBUKhEZGYlly5Zhy5YttT6wEBERUe3jsDMt/2S1WpGZmQmLxYIGDRrYnda/NuOZFiIioqqp1WdamjRpgmbNmuHPP//8a8MKBXx9fREQECC7wEJERES1j0PSxLVr16BWq9G8eXNHbI6IiIioDIecaQkODkY1XWUiIiIiAuCg0BIVFYX8/HzbM4iIiIiIHM0hoWXq1Klwc3PDc889h/z8fEdskoiIiKgUh4xpcXFxwZdffomnnnoK7dq1w/PPP4+ePXvC398fSqWy3PUaNmzoiN0TERFRPeCQW55vFkzK3bEkoaio6HZ3XW14yzMREVHV1OpbnksmkavMy2q1Vno/8+fPR+PGjaHVatGjRw8cPHiwQuutXLkSkiRh2LBhld4nERER1Q4OuTwUFxfniM3c1KpVqxAdHY1FixahR48emDt3LgYMGIBz587B39+/3PUuX76MV199FXfeeWe110hERETV57YvD1mtVpw9exbZ2dnw8fFBy5YtHVVbKT169EC3bt0wb948237DwsLw/PPPY+rUqXbXsVgsuOuuuzBx4kTs27cPWVlZ2LBhQ4X2x8tDREREVVPrLg+ZzWZMmTIFPj4+aN++PXr16oXWrVvDz88Ps2fPdui8LSaTCYcPH0ZUVJStTaFQICoqCjExMeWu9+6778Lf3x+PP/74LfdhNBqRnZ1d6kVERES1R5UvDw0bNgxbtmwpE04yMjIwY8YMXLhwAcuWLbvd+gAA6enpsFgsCAgIKNUeEBCAs2fP2l1n//79+PrrrxEbG1uhfcyZMwfvvPPO7ZZKRERE1aRKZ1rWrFmDX375BUIING/eHNOmTcP8+fPx2muv2WbH/e9//4s9e/Y4ut4KycnJwZgxY/DVV1/B19e3QutMmzYNBoPB9kpISKjmKomIiKgyqnSm5bvvvgMA3Hvvvfjxxx+h0Whsy6ZPn47+/fvj6NGj+P7779GnT5/bLtLX1xdKpRIpKSml2lNSUhAYGFim/8WLF3H58mUMHjzY1lZyt5KLiwvOnTuHZs2alVpHo9GU+hxERERUu1TpTMuRI0cgSRI+//zzMl/0np6e+PDDDyGEcNi0/mq1Gl26dMGOHTtsbVarFTt27EBkZGSZ/uHh4Thx4gRiY2NtryFDhqBfv36IjY1FWFiYQ+oiIiKimlOlMy3p6enQarVo3bq13eVdu3a19XOU6OhojBs3Dl27dkX37t0xd+5c5OXlYcKECQCAsWPHIiQkBHPmzIFWq0W7du1Kre/l5QUAZdqJiIhIHqoUWoxGo93LMiX0er2tn6OMHDkSaWlpmDFjBpKTk9GxY0ds2bLFNjj3ypUrUCgcMlceERER1UJVmqdFoVAgMDAQV69eva0+tRnnaSEiIqqaWjdPCxEREVFNqvI8LSkpKTd9UKIkSTftU9sfmEhERES1S5VDiyNnvCUiIiK6lSqFlpkzZzq6DiIiIqKbuu0HJtZVHIhLRERUNRyIS0RERPUaQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJgqxCy/z589G4cWNotVr06NEDBw8eLLfvV199hTvvvBPe3t7w9vZGVFTUTfsTERFR7Sab0LJq1SpER0dj5syZOHLkCCIiIjBgwACkpqba7b9792488sgj2LVrF2JiYhAWFoZ7770XSUlJNVw5EREROYIkhBDOLqIievTogW7dumHevHkAAKvVirCwMDz//POYOnXqLde3WCzw9vbGvHnzMHbs2DLLjUYjjEaj7X12djbCwsJgMBjg6enpuA9CRERUx2VnZ0Ov1zv8O1QWZ1pMJhMOHz6MqKgoW5tCoUBUVBRiYmIqtI38/HyYzWb4+PjYXT5nzhzo9XrbKywszCG1ExERkWPIIrSkp6fDYrEgICCgVHtAQACSk5MrtI0pU6YgODi4VPD5u2nTpsFgMNheCQkJt103EREROY6LswuoCR988AFWrlyJ3bt3Q6vV2u2j0Wig0WhquDIiIiKqKFmEFl9fXyiVSqSkpJRqT0lJQWBg4E3X/eSTT/DBBx9g+/bt6NChQ3WWSURERNVIFpeH1Go1unTpgh07dtjarFYrduzYgcjIyHLX++ijjzBr1ixs2bIFXbt2rYlSiYiIqJrI4kwLAERHR2PcuHHo2rUrunfvjrlz5yIvLw8TJkwAAIwdOxYhISGYM2cOAODDDz/EjBkzsHz5cjRu3Ng29sXd3R3u7u5O+xxERERUNbIJLSNHjkRaWhpmzJiB5ORk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ahteHiIicrI333wT27dvx4EDB9CiRQvceeediI+Pxx9//AE/Pz8sXbq0VP/09HScO3cO165dK7Otl156CadPn0ZERAT8/PyQkJCAmJgYSJKEL7/8Ev369SvVPzY2Fq+88goAoEmTJpg9e7bdGp944gn07t3bQZ+YqGoYWoiInEyr1WLXrl2YM2cOli9fjg0bNsDHxwfjx4/HrFmzyp14zp7HHnsM3333HY4dO4asrCz4+flh5MiReO2119CxY8cy/bOysiCEAADExMQgJibG7nb79u3L0EJOJ4mSv61USnZ2NvR6PQwGAzw9PZ1dDlGdUFhYiLi4ODRp0gRardbZ5RBRJVX033B1fYdyTAsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxGRk0mSVOalUqkQHByM4cOH48CBA84usUJ2794NSZIwfvz4Uu3Lli2DJEl4++23nVKXI1ksFnz++edo3749dDod/Pz88PDDD+PMmTNV3mZaWhpeffVVtGrVCjqdDj4+PujcuTNee+21Mn1LjmV5r1GjRpVZJz4+Hq+88gruuusuhIaGQqvVwt3dHZ06dcLs2bORl5dX5dprmouzCyAiomLjxo2z/ZyTk4Njx45h3bp1WL9+Pb777juMHj3aidWR1WrFiBEjsH79enh5eWHQoEFIT0/H2rVrsWnTJuzatQvdu3ev1DYPHz6MAQMGICMjA23btsXQoUORnZ2N06dP4/PPP8fHH39sd72IiAh07NixTHuPHj3KtJ04cQKfffYZAgMDER4ejjvvvBPXr1/H77//jjfffBMrVqzAvn374O3tXananUKQXQaDQQAQBoPB2aUQ1RkFBQXi9OnToqCgwNml1CoAhL1fxxaLRUyZMkUAEA0aNBAmk8kJ1VXcrl27BAAxbty4Uu1ZWVnizJkzIi0tzTmFOchXX30lAIgWLVqI5ORkW/vatWsFANG8eXNhNpsrvL3U1FTh6+srXF1dxY8//lhm+R9//FGm7ZtvvhEAxMyZMyu8n6tXr4qTJ0+WaTcYDOLuu+8WAMQrr7xSoW1V9N9wdX2H8vIQEVEtpVAo8O6778LFxQUZGRk4deqUs0uqEr1e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NjIzM0u9f/zxxwEAp06dwsKFCytUh6enp+2y0rFjx8osLyoq4vwsRDLC0EJE1WLatGmQJAmXL1/G119/DQB4//33ERwcjPT0dERGRuK///0vcnJybOukpaXhhx9+wAMPPIBHHnmk1Pb69euHUaNGAQCee+45TJs2rdTdPunp6ViyZIkt3ADFs9/26tULABAdHY2dO3fCarUCAE6ePImBAwfi0KFDcHNzq56DQESO5ZTZYYhItm42udw/DR06VAAQoaGhorCwUAghxOnTp0XLli1t21AoFMLHx0e4ubnZ2gCIqKioMtvLy8sTDz74YKl+np6eQq/X297/c1K6Q4cOldq2RqMRHh4eAoBwcXER//nPf0SjRo0EAPHNN9+U2Sc4uRxRrcEzLURUbUouvSQmJuLLL78EALRu3RrHjx/Hl19+iXvvvRe+vr7Izs6GEALNmzfHiBEjsHjxYqxevbrM9lxdXfHDDz/g559/xgMPPIDg4GAUFhbCxcUFHTp0wAsvvIDFixeXWqdLly44ePAgHn74Yfj6+sJqtcLDwwMPP/wwDhw4gDFjxlT/gSAih5CE4D1+REREVPvxTAsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJAkMLERERyQJDCxEREckCQwsRERHJwv8DVobSzMqgGdAAAAAASUVORK5CYII=","text/plain":["<Figure size 600x600 with 1 Axes>"]},"metadata":{},"output_type":"display_data"},{"data":{"image/png":"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","text/plain":["<Figure size 600x600 with 1 Axes>"]},"metadata":{},"output_type":"display_data"}],"source":["# from utils import get_roc_curve, get_pr_curve, get_confusion_matrix\n","\n","stage='test'\n","comment='patient'\n","pr_plot = get_pr_curve(probs, target, task=task)\n","pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.png', dpi=400)\n","pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.svg', format='svg')\n","plt.show()\n","\n","pr_plot.figure.clf()\n","\n","roc_plot = get_roc_curve(probs, target, task=task)\n","roc_plot.legend(loc='lower right', fontsize=15)\n","roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.png', dpi=400)\n","roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.svg', format='svg')\n","plt.show()\n","\n","roc_plot.figure.clf()"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Optimal Threshold test patient:  0.7783203125\n"]},{"data":{"image/png":"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314ff/yxbrjhhvLOCAAA4FLmNS/Dhw9XQUGBkpKSlJ6ervT0dCUlJcnpdGr48OEVkREAAMClzDMvX375pbZt26aoqCjXWFRUlN555x117NixXMMBAABcrswzL/Xq1SvxZnRFRUWqU6dOuYQCAAC4kjKXlzfeeENPPfWUdu7c6RrbuXOnRo8erTfffLNcwwEAAFzOYRiGca2dqlatKofD4Xp97tw5FRYWysvrwlmni/9dpUoVpaenV1zaUuo4dYvZEQBUkLWjbzc7AoAK4lfKxSyl2u1vf/vb74gCAABQfkpVXmJjYys6BwAAQKn85pvUSVJubq7y8/PdxoKDg39XIAAAgKsp84Ldc+fO6cknn1StWrVUpUoVVa1a1e0LAACgIpW5vDz33HNav3693nvvPfn6+uqDDz7QxIkTVadOHc2ePbsiMgIAALiU+bTR8uXLNXv2bN1xxx16+OGH1bFjR0VGRioiIkJz587V4MGDKyInAACApN8w85Kenq5GjRpJurC+5eKl0bfffrs2bdpUvukAAAAuU+by0qhRIx09elSSdOONN2rBggWSLszIXHxQIwAAQEUpc3l5+OGHtW/fPknSuHHj9I9//EN+fn4aM2aMnn322XIPCAAAcKlS3WH3av7zn/9o165dioyM1M0331xeuX4X7rAL/HFxh13gj6tc77B7NREREYqIiPi9hwEAACiVUpWXadOmlfqAo0aN+s1hAAAArqVUp40aNmxYuoM5HDpy5MjvDvV7Td+eYnYEABVkzEieXg/8UZ3f826p9ivVzMvFq4sAAADMVuarjQAAAMxEeQEAALZCeQEAALZCeQEAALZCeQEAALbym8rL5s2bNWTIELVr107Hjx+XJM2ZM0dbtnBnWwAAULHKXF4WLVqkHj16yN/fX3v27FFeXp4kKTMzU6+++mq5BwQAALhUmcvLyy+/rOnTp+v999+Xt7e3a7xDhw7avXt3uYYDAAC4XJnLS3Jysjp16lRsPCQkRBkZGeWRCQAA4IrKXF7Cw8N16NChYuNbtmxRo0aNyiUUAADAlZS5vIwYMUKjR4/Wjh075HA4dOLECc2dO1dxcXF64oknKiIjAACAS6mebXSpcePGyel06q677lJOTo46deokX19fxcXF6amnnqqIjAAAAC6leqp0SfLz83Xo0CFlZ2erWbNmCgwMLO9svxlPlQb+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AAAsyNSZl8TERG3atEnr1q1Tly5d3LatX79effv21ezZszV06FCTEgIAAKsxdeZl3rx5ev7554sVF0m68847NW7cOM2dO9eEZAAAwKpMLS/ffvutevbsecXtd999N0+VBgAAbkwtL+np6QoLC7vi9rCwMJ05c6YSEwEAAKsztbwUFRXJy+vKy248PT1VWFhYiYkAAIDVmbpg1zAMDRs2TL6+viVuz8vLq+REAADA6kwtL7GxsdfchyuNAADApUwtLzNnzjTz4wEAgA2ZuuYFAACgrCgvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVigvAADAVhyGYRhmhwB+q7y8PCUkJGj8+PHy9fU1Ow6AcsTPN66E8gJby8rKUkhIiDIzMxUcHGx2HADliJ9vXAmnjQAAgK1QXgAAgK1QXgAAgK1QXmBrvr6+eumll1jMB/wB8fONK2HBLgAAsBVmXgAAgK1QXgAAgK1QXgAAgK1QXoASbNy4UQ6HQxkZGWZHAWzN4XBo6dKlZsfAHwzlBRVu2LBhcjgceu2119zGly5dKofDYVIqAL/HxZ9rh8Mhb29vhYWFqVu3bvroo4/kdDpd+6Wlpenuu+82MSn+iCgvqBR+fn56/fXXdebMmXI7Zn5+frkdC0DZ9ezZU2lpaUpJSdHq1avVpUsXjR49Wvfee68KCwslSeHh4VzqjHJHeUGl6Nq1q8LDw5WQkHDFfRYtWqTmzZvL19dXDRo00NSpU922N2jQQJMnT9bQoUMVHBys//qv/1JiYqJCQ0O1YsUKRUVFKSAgQAMHDlROTo5mzZqlBg0aqGrVqho1apSKiopcx5ozZ47atm2roKAghYeH68EHH9TPP/9cYd8/8Efk6+ur8PBw1a1bVzExMXr++ef16aefavXq1UpMTJTkftooPz9fTz75pGrXri0/Pz9FRES4/U7IyMjQ8OHDVbNmTQUHB+vOO+/Uvn37XNsPHz6sPn36KCwsTIGBgbrlllv0xRdfuGX65z//qSZNmsjPz09hYWEaOHCga5vT6VRCQoIaNmwof39/tWzZUgsXLqy4PyBUGMoLKoWnp6deffVVvfPOOzp27Fix7bt27dKgQYP0l7/8Rfv379eECRMUHx/v+gV40ZtvvqmWLVtqz549io+PlyTl5ORo2rRp+vjjj7VmzRpt3LhR/fr106pVq7Rq1SrNmTNHM2bMcPslVVBQoMmTJ2vfvn1aunSpUlJSNGzYsIr8IwCuC3feeadatmypxYsXF9s2bdo0LVu2TAsWLFBycrLmzp2rBg0auLb/+c9/1s8//6zVq1dr165diomJ0V133aX09HRJUnZ2tnr16qV169Zpz5496tmzp+677z6lpqZKknbu3KlRo0Zp0qRJSk5O1po1a9SpUyfX8RMSEjR79mxNnz5d33//vcaMGaMhQ4boyy+/rNg/FJQ/A6hgsbGxRp8+fQzDMIzbbrvNeOSRRwzDMIwlS5YYF/8KPvjgg0a3bt3c3vfss88azZo1c72OiIgw+vbt67bPzJkzDUnGoUOHXGOPPfaYERAQYJw9e9Y11qNHD+Oxxx67YsZvvvnGkOR6z4YNGwxJxpkzZ8r+DQPXgUt/ri93//33G9HR0YZhGIYkY8mSJYZhGMZTTz1l3HnnnYbT6Sz2ns2bNxvBwcFGbm6u23jjxo2NGTNmXDFH8+bNjXfeeccwDMNYtGiRERwcbGRlZRXbLzc31wgICDC2bdvmNv7oo48aDzzwwBWPD2ti5gWV6vXXX9esWbOUlJTkNp6UlKQOHTq4jXXo0EEHDx50O93Ttm3bYscMCAhQ48aNXa/DwsLUoEEDBQYGuo1delpo165duu+++1S/fn0FBQWpc+fOkuT6FxyA384wjBIX4w8bNkx79+5VVFSURo0apc8//9y1bd++fcrOzlb16tUVGBjo+jp69KgOHz4s6cLMS1xcnKKjoxUaGqrAwEAlJSW5fm67deumiIgINWrUSA899JDmzp2rnJwcSdKhQ4eUk5Ojbt26uR1/9uzZruPDPrzMDoDrS6dOndSjRw+NHz/+N52mqVKlSrExb29vt9cXr364fOziFRDnzp1Tjx491KNHD82dO1c1a9ZUamqqevTowSJgoBwkJSWpYcOGxcZjYmJ09OhRrV69Wl988YUGDRqkrl27auHChcrOzlbt2rW1cePGYu8LDQ2VJMXFxWnt2rV68803FRkZKX9/fw0cOND1cxsUFKTdu3dr48aN+vzzz/Xiiy9qwoQJ+uabb5SdnS1JWrlyperWret2fBYU2w/lBZXutddeU6tWrRQVFeUai46O1tatW93227p1q5o2bSpPT89y/fwDBw7o119/1WuvvaZ69epJunCuHMDvt379eu3fv19jxowpcXtwcLDuv/9+3X///Ro4cKB69uyp9PR0xcTE6OTJk/Ly8nJbB3OprVu3atiwYerXr5+kCzMxKSkpbvt4eXmpa9eu6tq1q1566SWFhoZq/fr16tatm3x9fZWamuqaaYV9UV5Q6Vq0aKHBgwdr2rRprrFnnnlGt9xyiyZPnqz7779f27dv17vvvqt//vOf5f759evXl4+Pj9555x09/vjj+u677zR58uRy/xzgjy4vL08nT55UUVGRTp06pTVr1ighIUH33nuvhg4dWmz/t956S7Vr11br1q3l4eGhTz75ROHh4QoNDVXXrl3Vrl079e3bV1OmTFHTpk114sQJrVy5Uv369VPbtm3VpEkTLV68WPfdd58cDofi4+Pd7imzYsUKHTlyRJ06dVLVqlW1atUqOZ1ORUVFKSgoSHFxcRozZoycTqduv/12ZWZmauvWrQoODlZsbGxl/tHhd6K8wBSTJk3S/PnzXa9jYmK0YMECvfjii5o8ebJq166tSZMmVcgVQDVr1lRiYqKef/55TZs2TTExMXrzzTfVu3fvcv8s4I9szZo1ql27try8vFS1alW1bNlS06ZNU2xsrDw8ii+pDAoK0pQpU3Tw4EF5enrqlltu0apVq1z7rlq1Si+88IIefvhhnT59WuHh4erUqZPCwsIkXSg/jzzyiNq3b68aNWpo7NixysrKch0/NDRUixcv1oQJE5Sbm6smTZpo3rx5at68uSRp8uTJqlmzphISEnTkyBGFhoa6LvGG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","text/plain":["<Figure size 640x480 with 1 Axes>"]},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":["<Figure size 640x480 with 0 Axes>"]},"metadata":{},"output_type":"display_data"}],"source":["stage = 'test'\n","comment='patient'\n","\n","cm_plot = get_confusion_matrix(probs, target, task=task, threshold_csv_path = threshold_csv_path)\n","\n","plt.show()\n","# cm_plot.figure.set(font_scale=18)\n","cm_plot.savefig(f'{output_dir}/{task}_cm.png', dpi=400)\n","cm_plot.savefig(f'{output_dir}/{task}_cm.svg', format='svg')\n","\n","# plt.savefig(f'{output_dir}/{task}_cm.png', dpi=400)\n","\n","cm_plot.clf()\n","\n","\n","\n"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[],"source":["# print((Path(output_dir)/task).stem)\n","# print(output_dir)\n","# model = 'vit'\n","# task = 'norm_rest'\n","# output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'\n","# out_dir = Path(output_dir)/task\n","# for i in Path(out_dir).iterdir():\n","#     if i.is_file():\n","#         name = i.name.rsplit('_', 1)[1]\n","#         # print(i)\n","#         # print(i.parents[0])\n","#         new_name = Path(output_dir) / f'{task}_{name}'\n","#         print(new_name)\n","#         i.rename(new_name)\n","    # print(new_name)\n"]},{"cell_type":"code","execution_count":9,"metadata":{},"outputs":[{"ename":"NameError","evalue":"name 'patient_score' is not defined","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)","\u001b[1;32m/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb Cell 8\u001b[0m in \u001b[0;36m<cell line: 3>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=0'>1</a>\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mscipy\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mstats\u001b[39;00m \u001b[39mimport\u001b[39;00m bootstrap\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=1'>2</a>\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39msklearn\u001b[39;00m\n\u001b[0;32m----> <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=2'>3</a>\u001b[0m res \u001b[39m=\u001b[39m bootstrap((patient_score\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy(), patient_target\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy()), sklearn\u001b[39m.\u001b[39mmetrics\u001b[39m.\u001b[39mroc_auc_score, confidence_level\u001b[39m=\u001b[39m\u001b[39m0.95\u001b[39m, paired\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m, vectorized\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m)\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a>\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m'\u001b[39m\u001b[39mbootstrap AUC: \u001b[39m\u001b[39m'\u001b[39m, res)\n\u001b[1;32m      <a href='vscode-notebook-cell://ssh-remote%2Bdgx2/home/ylan/workspace/TransMIL-DeepGraft/code/utils/export_metrics.ipynb#X10sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a>\u001b[0m patient_AUC \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mAUROC(patient_score, patient_target\u001b[39m.\u001b[39msqueeze())\n","\u001b[0;31mNameError\u001b[0m: name 'patient_score' is not defined"]}],"source":["from scipy.stats import bootstrap\n","import sklearn\n","res = bootstrap((patient_score.cpu().numpy(), patient_target.cpu().numpy()), sklearn.metrics.roc_auc_score, confidence_level=0.95, paired=True, vectorized=False)\n","\n","print('bootstrap AUC: ', res)\n","patient_AUC = self.AUROC(patient_score, patient_target.squeeze())"]}],"metadata":{"kernelspec":{"display_name":"Python 3 (ipykernel)","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.16"},"orig_nbformat":4,"vscode":{"interpreter":{"hash":"e036a9c91377bd599a54855c8808043bcb982545d7d4bb9989918e49d09c4e97"}}},"nbformat":4,"nbformat_minor":2}
diff --git a/code/utils/export_metrics.py b/code/utils/export_metrics.py
index 2017ebfe3d80e567ecf17b6d2cf2803146cb8fcd..fae2c6edc2d31e954c0b8b2a444f817875ebd3c7 100644
--- a/code/utils/export_metrics.py
+++ b/code/utils/export_metrics.py
@@ -1,4 +1,4 @@
-# %%
+
 import argparse
 from pathlib import Path
 import numpy as np
@@ -17,175 +17,259 @@ import torch
 import torchmetrics
 from torchmetrics import PrecisionRecallCurve, ROC
 from torchmetrics.functional.classification import binary_auroc, multiclass_auroc, binary_precision_recall_curve, multiclass_precision_recall_curve, confusion_matrix
+from torchmetrics.functional.classification import binary_accuracy, multiclass_accuracy, binary_recall, binary_precision, multiclass_recall, multiclass_precision, binary_f1_score, multiclass_f1_score
 from torchmetrics.utilities.compute import _auc_compute_without_check, _auc_compute
 
-
-# %%
-'''TransMIL'''
-a = 'features'
-add_on = '0'
-
-# task = 'norm_rest'
-# model = 'TransMIL'
-# version = '804'
-# epoch = '30'
-# labels = ['Normal']
-
-
-task = 'rest_rej'
-model = 'TransMIL'
-version = '63'
-epoch = '14'
-labels = ['Rejection']
-
-# task = 'norm_rej_rest'
-# model = 'TransMIL'
-# version = '53'
-# epoch = '17'
-# labels = ['Normal', 'Rejection', 'Rest']
-
-'''ViT'''
-# a = 'vit'
-
-# task = 'norm_rest'
-# model = 'vit'
-# version = '16'
-# epoch = '142'
-# labels = ['Disease']
-
-# task = 'rej_rest'
-# model = 'vit'
-# version = '1'
-# epoch = 'last'
-# labels = ['Rest']
-
-# task = 'norm_rej_rest'
-# model = 'vit'
-# version = '0'
-# epoch = '226'
-# labels = ['Normal', 'Rejection', 'Rest']
-
-'''CLAM'''
-# task = 'norm_rest'
-# model = 'CLAM'
-# labels = ['REST']
-
-# task = 'rej_rest'
-# model = 'CLAM'
-# labels = ['REST']
-
-# task = 'norm_rej_rest'
-# model = 'CLAM'
-# labels = ['NORMAL', 'REJECTION', 'REST']
-# labels = ['Normal', 'Rejection', 'Rest']
-# if task == 'norm_rest' or task == 'rej_rest':
-#     n_classes = 2
-#     PRC = torchmetrics.PrecisionRecallCurve(task='binary')
-#     ROC = torchmetrics.ROC(task='binary')
-# else: 
-#     n_classes = 3
-#     PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)
-#     ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
-
-
-
-
-# %%
-'''Find Directory'''
-
-home = Path.cwd().parts[1]
-root_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/{model}/{task}/_{a}_CrossEntropyLoss/lightning_logs/version_{version}/test_epoch_{epoch}'
-print(root_dir)
-patient_result_csv_path = Path(root_dir) / 'TEST_RESULT_PATIENT.csv'
-# threshold_csv_path = f'{root_dir}/val_thresholds.csv'
-
-# patient_result_csv_path = Path(f'/{home}/ylan/workspace/HIA/logs/DeepGraft_Lancet/clam_mb/DEEPGRAFT_CLAMMB_TRAINFULL_{task}/RESULTS/TEST_RESULT_PATIENT_BASED_FULL.csv')
-# threshold_csv_path = ''
-
-output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'
-Path(output_dir).mkdir(parents=True, exist_ok=True)
-
-patient_result = pd.read_csv(patient_result_csv_path)
-pprint.pprint(patient_result)
-probs = torch.from_numpy(np.array(patient_result[labels]))
-# probs = probs.squeeze()
-# if task == 'rest_rej':
-    
-# probs = torch.transpose(probs, 0,1).squeeze()
-target = torch.from_numpy(np.array(patient_result.yTrue))
-if add_on == '0':
-    target = -1 * (target-1)
-
-# 
-# target = torch.stack((fake_target, target), dim=1)
-# print(target.shae)
-# print(target)
-# target = -1 * (target-1)
-# print(target)
-
-
-
-# %%
-from utils import get_roc_curve, get_pr_curve, get_confusion_matrix
-
-# %%
-from utils import get_roc_curve, get_pr_curve, get_confusion_matrix
-
-print(probs.shape)
-print(target.shape)
-
-stage='test'
-comment='patient'
-pr_plot = get_pr_curve(probs, target, task=task)
-pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.png', dpi=400)
-pr_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_pr.svg', format='svg')
-plt.show()
-
-pr_plot.figure.clf()
-
-roc_plot = get_roc_curve(probs, target, task=task)
-roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.png', dpi=400)
-roc_plot.figure.savefig(f'{output_dir}/{task}_{add_on}_roc.svg', format='svg')
-plt.show()
-
-roc_plot.figure.clf()
-
-# %%
-stage = 'test'
-comment='patient'
-
-
-cm_plot = get_confusion_matrix(probs, target, task=task, threshold_csv_path = threshold_csv_path)
-
-# plt.show()
-# cm_plot.figure.set(font_scale=18)
-cm_plot.savefig(f'{output_dir}/{task}_cm.png', dpi=400)
-cm_plot.savefig(f'{output_dir}/{task}_cm.svg', format='svg')
-
-# plt.savefig(f'{output_dir}/{task}_cm.png', dpi=400)
-
-cm_plot.clf()
-
-
-
-
-
-# %%
-# print((Path(output_dir)/task).stem)
-# print(output_dir)
-# model = 'vit'
-# task = 'norm_rest'
-# output_dir = f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'
-# out_dir = Path(output_dir)/task
-# for i in Path(out_dir).iterdir():
-#     if i.is_file():
-#         name = i.name.rsplit('_', 1)[1]
-#         # print(i)
-#         # print(i.parents[0])
-#         new_name = Path(output_dir) / f'{task}_{name}'
-#         print(new_name)
-#         i.rename(new_name)
-    # print(new_name)
-
-
-
+from utils import get_roc_curve, get_pr_curve, get_confusion_matrix, get_optimal_operating_point
+
+from scipy.stats import bootstrap
+from sklearn.metrics import roc_auc_score
+import statistics
+import logging
+
+
+def bootstrap(y_pred, y_true, n):
+    rng_seed = 42
+    bootstrapped_scores = []
+    bootstrapped_accuracy = []
+    n_classes = 1
+    if len(y_pred.shape) > 1:
+        n_classes = y_pred.shape[1]
+    # rng = np.random.RandomState(rng_seed=4)
+    for i in range(n):
+        # bootstrap by sampling with replacement on the prediction indices
+        indices = torch.randint(low=0, high=len(y_true), size=(len(y_true), ))
+        if len(np.unique(y_true[indices])) < 2:
+            # We need at least one positive and one negative sample for ROC AUC
+            # to be defined: reject the sample
+            continue
+        if len(y_pred.shape) > 1:
+            score = multiclass_auroc(y_pred[indices], y_true[indices], num_classes=n_classes, average=None)
+        else: 
+            score = binary_auroc(y_pred[indices], y_true[indices])
+
+        bootstrapped_scores.append(score)
+
+    if n_classes > 1:
+        for i in range(n_classes):
+            print('Class ', i)
+
+            sub_array = [x[i] for x in bootstrapped_scores]
+            # print(sub_array)
+            sorted_scores = np.array(sub_array)
+            sorted_scores.sort()
+
+        # Computing the lower and upper bound of the 90% confidence interval
+        # You can change the bounds percentiles to 0.025 and 0.975 to get
+        # a 95% confidence interval instead.
+            confidence_lower = sorted_scores[int(0.025 * len(sorted_scores))]
+            confidence_upper = sorted_scores[int(0.975 * len(sorted_scores))]
+
+
+            print("n={} Confidence interval for the score: [{:0.3f} - {:0.3}]".format(n,
+            confidence_lower, confidence_upper))
+
+            print('MEAN: ', np.mean(sorted_scores))
+            print('MEDIAN: ', statistics.median(sorted_scores))
+
+        mean_array = [torch.mean(x) for x in bootstrapped_scores]
+
+        sorted_scores = np.array(mean_array)
+        sorted_scores.sort()
+
+        confidence_lower = sorted_scores[int(0.025 * len(sorted_scores))]
+        confidence_upper = sorted_scores[int(0.975 * len(sorted_scores))]
+
+
+        print("n={} MEAN Confidence interval for the score: {:0.3f}".format(n,
+        confidence_lower, confidence_upper))
+        
+    else:
+
+        sorted_scores = np.array(bootstrapped_scores)
+        
+        sorted_scores.sort()
+        confidence_lower = sorted_scores[int(0.025 * len(sorted_scores))]
+        confidence_upper = sorted_scores[int(0.975 * len(sorted_scores))]
+
+        print("n={} Confidence interval for the score: [{:0.3f} - {:0.3}]".format(n,
+        confidence_lower, confidence_upper))
+
+        print('MEAN: ', np.mean(sorted_scores))
+        print('MEDIAN: ', statistics.median(sorted_scores))
+
+def make_parse():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--model', default='TransMIL', type=str)
+    parser.add_argument('--task', default='norm_rest',type=str)
+    parser.add_argument('--target_label', default = 1, type=int)
+    args = parser.parse_args()
+    return args
+
+
+args = make_parse()
+
+ckpt_dict = {
+    'TransMIL': {
+        # 'norm_rest': {'version': '893', 'epoch': '166', 'labels':['Normal', 'Disease']},
+        'norm_rest': {'version': '804', 'epoch': '30', 'labels':['Normal', 'Disease']},
+        'rest_rej': {'version': '63', 'epoch': '14', 'labels': ['Rest', 'Rejection']},
+        'norm_rej_rest': {'version': '53', 'epoch': '17', 'labels': ['Normal', 'Rejection', 'Rest']},
+    },
+    'vit': {
+        'norm_rest': {'version': '16', 'epoch': '142', 'labels':['Normal', 'Disease']},
+        'rej_rest': {'version': '1', 'epoch': 'last', 'labels': ['Rejection', 'Rest']},
+        'norm_rej_rest': {'version': '0', 'epoch': '226', 'labels': ['Normal', 'Rejection', 'Rest']},
+    },
+    'CLAM': {
+        'norm_rest': {'labels':['NORMAL', 'REST']},
+        'rej_rest': {'labels': ['REJECTION', 'REST']},
+        'norm_rej_rest': {'labels': ['NORMAL', 'REJECTION', 'REST']},
+    }
+}
+def generate_plots(model, task, version=None, epoch=None, labels=None, add_on=0):
+
+    print('-----------------------------------------------------------------------')
+    print(model, task, version, epoch, labels, add_on)
+    print('-----------------------------------------------------------------------')
+
+
+
+    if model == 'CLAM':
+        patient_result_csv_path = Path(f'/homeStor1/ylan/workspace/HIA/logs/DeepGraft_Lancet/clam_mb/DEEPGRAFT_CLAMMB_TRAINFULL_{task}/RESULTS/TEST_RESULT_PATIENT_BASED_FULL.csv')
+        threshold_csv_path = ''
+    else: 
+        #TransMIL and ViT
+        root_dir = f'/homeStor1/ylan/workspace/TransMIL-DeepGraft/logs/DeepGraft/{model}/{task}/_{a}_CrossEntropyLoss/lightning_logs/version_{version}/test_epoch_{epoch}'
+        # print(root_dir)
+        patient_result_csv_path = Path(root_dir) / 'TEST_RESULT_PATIENT.csv'
+        # print(patient_result_csv_path)
+        threshold_csv_path = f'{root_dir}/val_thresholds.csv'
+        thresh_df = pd.read_csv(threshold_csv_path, index_col=False)
+        optimal_threshold = thresh_df['patient'].values[0]
+        
+        # threshold = 
+    #####
+    ######
+
+
+    # output_dir = f'/homeStor1/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'
+    output_dir = f'/homeStor1/ylan/DeepGraft_project/DeepGraft_Draft/figures/{model}'
+    Path(output_dir).mkdir(parents=True, exist_ok=True)
+
+    patient_result = pd.read_csv(patient_result_csv_path)
+    # pprint.pprint(patient_result)
+
+    probs = np.array(patient_result[labels[int(add_on)]])
+    if task == 'norm_rej_rest':
+        probs = np.array(patient_result[labels])
+    probs = probs.squeeze()
+    probs = torch.from_numpy(probs)
+
+        
+    # probs = torch.transpose(probs, 0,1).squeeze()
+    target = np.array(patient_result.yTrue, dtype=int)
+    target = torch.from_numpy(target)
+
+    # res = bootstrap((probs.cpu().numpy(), target.cpu().numpy()), sklearn.metrics.roc_auc_score, confidence_level=0.95, paired=True, vectorized=False)
+
+    # print('bootstrap AUC: ', res)
+    # patient_AUC = self.AUROC(patient_score, patient_target.squeeze())
+
+    #swap values for rest_rej for it to align
+    if task == 'rest_rej':
+        probs = 1 - probs
+        target = -1 * (target-1)
+        task = 'rej_rest'
+    # 
+    if add_on == 0 and task != 'norm_rej_rest':
+        probs = 1 - probs
+        # target = 1 - target
+    # if task == 'norm_rej_rest':
+    #         optimal_threshold = 1/3
+    # else:
+    # if model == 'CLAM':
+    #     if task == 'norm_rej_rest':
+    #         optimal_threshold = 1/3
+    #     else: optimal_threshold = 0.5
+    # else: 
+    if task == 'norm_rej_rest':
+        optimal_threshold = 1/3
+    else: optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(probs.unsqueeze(0), target.unsqueeze(0))
+    if task != 'norm_rej_rest':
+        accuracy = binary_accuracy(probs, target, threshold=optimal_threshold)
+        recall = binary_recall(probs, target, threshold=optimal_threshold)
+        precision = binary_precision(probs, target, threshold=optimal_threshold)
+        f1 = binary_f1_score(probs, target, threshold=optimal_threshold)
+    else: 
+        accuracy = multiclass_accuracy(probs, target, num_classes=3, average=None)
+        recall = multiclass_recall(probs, target, num_classes=3, average=None)
+        precision = multiclass_precision(probs, target, num_classes=3, average=None)
+        f1 = multiclass_f1_score(probs, target, num_classes=3, average=None)
+
+
+    print(f'Threshold: {optimal_threshold}')
+    print('Accuracy: ', accuracy)
+    print('Recall: ', recall)
+    print('Precision: ', precision)
+    print('F1: ', f1)
+
+    bootstrap(probs, target, n=1000)
+
+
+    ######################################################################################
+    # Plot
+    ######################################################################################
+
+
+    # probs = 1-probs
+
+    stage='test'
+    comment='patient'
+
+    # for i in range(len(labels)):
+    pr_plot = get_pr_curve(probs, target, task=task, model=model, target_label=add_on)
+    if task != 'norm_rej_rest':
+        pr_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_pr.png', dpi=400)
+        pr_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_pr.svg', format='svg')
+    pr_plot.figure.clf()
+
+    # roc_plot = get_roc_curve(probs, target, task=task, model=model)
+    # if task != 'norm_rej_rest':
+    #     roc_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_roc.png', dpi=400)
+    #     roc_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_roc.svg', format='svg')
+    # roc_plot.figure.clf()
+
+    # cm_plot, _ = get_confusion_matrix(probs, target, task=task, optimal_threshold=optimal_threshold)
+    # cm_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_cm.png', dpi=400)
+    # cm_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_cm.svg', format='svg')
+    # cm_plot.figure.clf()
+
+    # plt.close()
+
+
+if __name__ == '__main__':
+
+    args = make_parse()
+    for model in ckpt_dict.keys():
+        for task in ckpt_dict[model].keys():
+            labels = ckpt_dict[model][task]['labels']
+            for i in range(len(labels)):
+                add_on = i
+                if model == 'TransMIL':
+                    a = 'features'
+                    version = ckpt_dict[model][task]['version']
+                    epoch = ckpt_dict[model][task]['epoch']
+                    labels = ckpt_dict[model][task]['labels']
+                elif model == 'vit':
+                    a = 'vit'
+                    version = ckpt_dict[model][task]['version']
+                    epoch = ckpt_dict[model][task]['epoch']
+                    labels = ckpt_dict[model][task]['labels']
+                elif model == 'CLAM':
+                    labels = ckpt_dict[model][task]['labels']
+
+                generate_plots(model=model, task=task, version=version, epoch=epoch, labels=labels, add_on=add_on)
+        #         break
+        #     break
+        # break
diff --git a/code/utils/utils.py b/code/utils/utils.py
index fca7597b789859c27fe00c0b075c8eee52fdd6f3..c011a8bdae2a48461345b10d42c5fcf516c668e8 100644
--- a/code/utils/utils.py
+++ b/code/utils/utils.py
@@ -14,7 +14,7 @@ from pytorch_lightning import LightningModule
 # from pytorch_lightning.loops.base import Loop
 # from pytorch_lightning.loops.fit_loop import FitLoop
 from pytorch_lightning.trainer.states import TrainerFn
-from pytorch_lightning.callbacks import LearningRateMonitor
+from pytorch_lightning.callbacks import LearningRateMonitor, BatchSizeFinder, DeviceStatsMonitor
 from typing import Any, Dict, List, Optional, Type
 import shutil
 
@@ -25,11 +25,17 @@ import json
 import pprint
 import seaborn as sns
 
+import numpy as np
+
 import torchmetrics
 from torchmetrics import PrecisionRecallCurve, ROC
-from torchmetrics.functional.classification import binary_auroc, multiclass_auroc, binary_precision_recall_curve, multiclass_precision_recall_curve, confusion_matrix
+from torchmetrics.functional.classification import binary_roc, binary_auroc, multiclass_auroc, binary_precision_recall_curve, multiclass_precision_recall_curve, confusion_matrix
 from torchmetrics.utilities.compute import _auc_compute_without_check, _auc_compute
 
+LEGEND_SIZE = 50
+AXIS_SIZE = 40
+
+
 LABEL_MAP = {
     # 'bin': {'0': 0, '1': 1, '2': 1, '3': 1, '4': 1, '5': None},
     # 'tcmr_viral': {'0': None, '1': 0, '2': None, '3': None, '4': 1, '5': None},
@@ -39,9 +45,9 @@ LABEL_MAP = {
     # 'all': {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5},
     'rejections': {'0': 'TCMR', '1': 'ABMR', '2': 'Mixed'},
     'norm_rest': {'0': 'Normal', '1': 'Disease'},
-    'rej_rest': {'0': 'Rejection', '1': 'Rest'},
-    'rest_rej': {'0': 'Rest', '1': 'Rejection'},
-    'norm_rej_rest': {'0': 'Normal', '1': 'Rejection', '2': 'Rest'},
+    'rej_rest': {'0': 'Rejection', '1': 'Other'},
+    'rest_rej': {'0': 'Other', '1': 'Rejection'},
+    'norm_rej_rest': {'0': 'Normal', '1': 'Rejection', '2': 'Other'},
 
 }
 COLOR_MAP = ['#377eb8', '#ff7f00', '#4daf4a',
@@ -91,6 +97,7 @@ def load_loggers(cfg):
                                         ) # version = f'fold{cfg.Data.fold}', 
         # print(csv_logger.version)
         # wandb_logger = pl_loggers.WandbLogger(project=f'{cfg.Model.name}_{cfg.task}', name=f'{cfg.log_name}', save_dir=cfg.log_path)
+        return [tb_logger, csv_logger]
     else:  
         if cfg.from_finetune:
             prefix = 'test_ft_epoch'
@@ -104,16 +111,18 @@ def load_loggers(cfg):
                                                 sub_dir = f'{prefix}_{cfg.epoch}',
                                                 log_graph = True, default_hp_metric = False)
         #---->CSV
-        # version = tb_logger.version
+        # for some reason this creates the save path.
+        version = tb_logger.version
         csv_logger_path = Path(cfg.log_path) / 'lightning_logs' / f'version_{cfg.version}' / f'test_epoch_{cfg.epoch}'
         csv_logger = pl_loggers.CSVLogger(csv_logger_path,
                                         version = cfg.version)
         # wandb_logger = pl_loggers.WandbLogger(project=f'{cfg.task}_{cfg.log_name}')                      
     
-    print(f'---->Log dir: {cfg.log_path}')
+        # print(f'---->Log dir: {cfg.log_path}')
 
     # return tb_logger
-    return [tb_logger, csv_logger]
+        # return [tb_logger]
+        return [tb_logger, csv_logger]
     # return wandb_logger
     # return [tb_logger, csv_logger, wandb_logger]
 
@@ -148,7 +157,6 @@ def load_callbacks(cfg, save_path):
             time='grey82',
             processing_speed='grey82',
             metrics='grey82'
-
         )
     )
     Mycallbacks.append(progress_bar)
@@ -184,32 +192,43 @@ def load_callbacks(cfg, save_path):
         else:
             Mycallbacks.append(ModelCheckpoint(monitor = 'val_loss',
                                             dirpath = str(output_path),
-                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_auc: .4f}-{val_patient_auc:.4f}',
+                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_accuracy:.2f}-{val_auc: .2f}-{val_patient_auc: .2f}',
                                             verbose = True,
                                             save_last = True,
                                             save_top_k = 3,
                                             mode = 'min',
                                             save_weights_only = True))
-            Mycallbacks.append(ModelCheckpoint(monitor = 'val_accuracy',
+            Mycallbacks.append(ModelCheckpoint(monitor = 'val_auc',
                                             dirpath = str(output_path),
-                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_accuracy:.4f}-{val_patient_auc: .4f}',
+                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_accuracy:.2f}-{val_auc: .2f}-{val_patient_auc: .2f}',
                                             verbose = True,
                                             save_last = True,
-                                            save_top_k = 3,
+                                            save_top_k = 1,
                                             mode = 'max',
                                             save_weights_only = True))
-            Mycallbacks.append(ModelCheckpoint(monitor = 'val_patient_auc',
+            Mycallbacks.append(ModelCheckpoint(monitor = 'val_accuracy',
                                             dirpath = str(output_path),
-                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_auc:.4f}-{val_patient_auc:.4f}',
+                                            filename = '{epoch:02d}-{val_loss:.4f}-{val_accuracy:.2f}-{val_auc: .2f}-{val_patient_auc: .2f}',
                                             verbose = True,
                                             save_last = True,
                                             save_top_k = 3,
                                             mode = 'max',
                                             save_weights_only = True))
+            # Mycallbacks.append(ModelCheckpoint(monitor = 'val_patient_auc',
+            #                                 dirpath = str(output_path),
+            #                                 filename = '{epoch:02d}-{val_loss:.4f}-{val_auc:.4f}-{val_patient_auc:.4f}',
+            #                                 verbose = True,
+            #                                 save_last = True,
+            #                                 save_top_k = 3,
+            #                                 mode = 'max',
+            #                                 save_weights_only = True))
 
     swa = StochasticWeightAveraging(swa_lrs=1e-2)
     Mycallbacks.append(swa)
 
+    # device_stats = DeviceStatsMonitor(cpu_stats=True)
+    # Mycallbacks.append(device_stats)
+
     lr_monitor = LearningRateMonitor(logging_interval='step')
     Mycallbacks.append(lr_monitor)
 
@@ -233,7 +252,8 @@ def convert_labels_for_task(task, label):
     return LABEL_MAP[task][label]
 
 
-def get_optimal_operating_point(fpr, tpr, thresholds):
+def get_optimal_operating_point(probs, target):
+# def get_optimal_operating_point(fpr, tpr, thresholds):
     '''
     Returns: 
         optimal_fpr [Tensor]
@@ -241,6 +261,8 @@ def get_optimal_operating_point(fpr, tpr, thresholds):
         optimal_threshold [Float]
     '''
 
+    fpr, tpr, thresholds = binary_roc(probs, target)
+
     youden_j = tpr - fpr
     optimal_idx = torch.argmax(youden_j)
     # print(youden_j[optimal_idx])
@@ -253,76 +275,142 @@ def get_optimal_operating_point(fpr, tpr, thresholds):
 
 
 
-def get_roc_curve(probs, target, task):
+def get_roc_curve(probs, target, task, model, separate=True):
+
+    if type(probs) is np.ndarray:
+        probs = torch.from_numpy(probs)
+    if type(target) is np.ndarray:
+        target = torch.from_numpy(target)
         
     task_label_map = LABEL_MAP[task]
-
-    if task == 'norm_rest' or task == 'rej_rest' or task == 'rest_rej':
-
+    
+    if len(probs.shape) == 1:
         n_classes = 2
-        # PRC = torchmetrics.PrecisionRecallCurve(task='binary')
         ROC = torchmetrics.ROC(task='binary')
-    else: 
+
+    else:
         n_classes = 3
-        # PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)
         ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
+        
+    # if task == 'norm_rest' or task == 'rej_rest' or task == 'rest_rej':
+
+    #     n_classes = 2
+    #     ROC = torchmetrics.ROC(task='binary')
+    # else: 
+    #     n_classes = 3
+    #     ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
 
     fpr_list, tpr_list, thresholds = ROC(probs, target)
 
-    # self.AUROC(out_probs, target.squeeze())
 
-    fig, ax = plt.subplots(figsize=(6,6))
+    
+    # print(probs)
+    # print(target)
+    # print(probs.shape)
+    # print(target.shape)
+    # fig, ax = plt.subplots(figsize=(6,6))
 
+    plots = []
     if n_classes > 2:
         auroc_score = multiclass_auroc(probs, target, num_classes=n_classes, average=None)
         for i in range(len(fpr_list)):
+            fig, ax = plt.subplots(figsize=(10,10))
+            # fig = plt.figure(figsize=(6,6))
 
             class_label = task_label_map[str(i)]
+            # color = COLOR_MAP[0]
             color = COLOR_MAP[i]
             
             fpr = fpr_list[i].cpu().numpy()
             tpr = tpr_list[i].cpu().numpy()
             # ax.plot(fpr, tpr, label=f'class_{i}, AUROC={auroc_score[i]}')
             df = pd.DataFrame(data = {'fpr': fpr, 'tpr': tpr})
-            line_plot = sns.lineplot(data=df, x='fpr', y='tpr', label=f'{class_label}={auroc_score[i]:.3f}', legend='full', color=color)
-        
+            # line_plot = sns.lineplot(data=df, x='fpr', y='tpr', label=f'{auroc_score[i]:.3f}', legend='full', color=color, linewidth=3)
+
+            ### temporary!!!
+            if separate:
+                color = COLOR_MAP[0]
+                line_plot = sns.lineplot(data=df, x='fpr', y='tpr', label=f'{auroc_score[i]:.3f}', legend='full', color=color, linewidth=3, )
+                add_on = i
+                # output_dir = f'/homeStor1/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'
+                output_dir = f'/homeStor1/ylan/DeepGraft_project/DeepGraft_Draft/figures/{model}'
+
+                ax.plot([0,1], [0,1], linestyle='--', color='red')
+                ax.set_xlim([0,1])
+                ax.set_ylim([0,1])
+                ax.set_xlabel('', fontsize=18)
+                # 
+                ax.set_ylabel('True positive rate (sensitivity)', fontsize=AXIS_SIZE)
+
+                # if i == 2:
+                ax.set_xlabel('False positive rate (1-specificity)', fontsize=AXIS_SIZE)
+                # else:
+                    # ax.set_xlabel('', fontsize=AXIS_SIZE)
+                ax.tick_params(axis='x', labelsize=25)
+                ax.tick_params(axis='y', labelsize=25)
+                # ax.set_yticklabels(fontsize=15)
+                # ax.set_title('ROC curve')
+                ax.legend(loc='lower right', fontsize=LEGEND_SIZE)
+
+                line_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_roc.png', dpi=400)
+                line_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_roc.svg', format='svg')
+            #     # plt.show()
+
+            #     line_plot.figure.clf()
+            #     plots.append(fig)
+            # else:
+
     else: 
+        fig, ax = plt.subplots(figsize=(10,10))
         auroc_score = binary_auroc(probs, target)
         color = COLOR_MAP[0]
         
-        optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(fpr_list, tpr_list, thresholds)
+        optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(probs, target)
         fpr = fpr_list.cpu().numpy()
         tpr = tpr_list.cpu().numpy()
         optimal_fpr = optimal_fpr.cpu().numpy()
         optimal_tpr = optimal_tpr.cpu().numpy()
 
         df = pd.DataFrame(data = {'fpr': fpr, 'tpr': tpr})
-        line_plot = sns.lineplot(data=df, x='fpr', y='tpr', label=f'{auroc_score:.3f}', legend='full', color=color) #AUROC
+        line_plot = sns.lineplot(data=df, x='fpr', y='tpr', label=f'{auroc_score:.3f}', legend='full', color=color, linewidth=3, errorbar=('ci', 95)) #AUROC
         # ax.plot([0, 1], [optimal_tpr, optimal_tpr], linestyle='--', color='black', label=f'OOP={optimal_threshold:.3f}')
         # ax.plot([optimal_fpr, optimal_fpr], [0, 1], linestyle='--', color='black')
+
+    # for fig in plots:
+        # ax = fig.add_subplot(111)
     ax.plot([0,1], [0,1], linestyle='--', color='red')
     ax.set_xlim([0,1])
     ax.set_ylim([0,1])
-    ax.set_xlabel('False positive rate (1-specificity)', fontsize=18)
-    ax.set_ylabel('True positive rate (sensitivity)', fontsize=18)
+    # ax.set_xlabel('', fontsize=18)
+    ax.set_xlabel('False positive rate (1-specificity)', fontsize=AXIS_SIZE)
+    ax.set_ylabel('True positive rate (sensitivity)', fontsize=AXIS_SIZE)
+    ax.tick_params(axis='x', labelsize=25)
+    ax.tick_params(axis='y', labelsize=25)
+    # ax.set_yticklabels(fontsize=15)
     # ax.set_title('ROC curve')
-    ax.legend(loc='lower right', fontsize=15)
+    ax.legend(loc='lower right', fontsize=LEGEND_SIZE)
 
+    # return plots
     return line_plot
 
-def get_pr_curve(probs, target, task):
+def get_pr_curve(probs, target, task, model, target_label=1):
+
+    if type(probs) is np.ndarray:
+        probs = torch.from_numpy(probs)
+    if type(target) is np.ndarray:
+        target = torch.from_numpy(target)
 
     if task == 'norm_rest' or task == 'rej_rest' or task == 'rest_rej':
         n_classes = 2 
-        PRC = torchmetrics.PrecisionRecallCurve(task='binary')
+        # PRC = torchmetrics.PrecisionRecallCurve(task='binary')
         # ROC = torchmetrics.ROC(task='binary')
     else: 
         n_classes = 3
-        PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)
+        # PRC = torchmetrics.PrecisionRecallCurve(task='multiclass', num_classes = n_classes)
         # ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
     
     
-    fig, ax = plt.subplots(figsize=(6,6))
+    fig, ax = plt.subplots(figsize=(10,10))
 
 
     
@@ -333,24 +421,51 @@ def get_pr_curve(probs, target, task):
         
         for i in range(len(precision)):
 
+            fig, ax = plt.subplots(figsize=(10,10))
+
             class_label = task_label_map[str(i)]
-            color = COLOR_MAP[i]
+            color = COLOR_MAP[0]
 
             re = recall[i]
             pr = precision[i]
             
-            partial_auc = _auc_compute(re, pr, 1.0)
+            # baseline = len(target[target==i]) / len(target)
+            partial_auc = _auc_compute(re, pr, 1.0) #- baseline
             df = pd.DataFrame(data = {'re': re.cpu().numpy(), 'pr': pr.cpu().numpy()})
-            line_plot = sns.lineplot(data=df, x='re', y='pr', label=f'{class_label}={partial_auc:.3f}', legend='full', color=color)
+            line_plot = sns.lineplot(data=df, x='re', y='pr', label=f'{partial_auc:.3f}', legend='full', color=color, linewidth=3)
 
             baseline = len(target[target==i]) / len(target)
-            ax.plot([0,1],[baseline, baseline], linestyle='--', label=f'Baseline={baseline:.3f}', color=color)
+            print(baseline)
+            ax.plot([0,1],[baseline, baseline], linestyle='--', color=color)
+
+            add_on = i
+            # output_dir = f'/homeStor1/ylan/workspace/TransMIL-DeepGraft/test/results/{model}/'
+            output_dir = f'/homeStor1/ylan/DeepGraft_project/DeepGraft_Draft/figures/{model}'
+            
+            # ax.plot([0,1], [0,1], linestyle='--', color='red')
+            ax.set_xlim([0,1])
+            ax.set_ylim([0,1])
+            # 
+            ax.set_xlabel('Precision', fontsize=AXIS_SIZE)
+            ax.set_ylabel('Recall', fontsize=AXIS_SIZE)
+            ax.tick_params(axis='x', labelsize=25)
+            ax.tick_params(axis='y', labelsize=25)
+            # ax.set_yticklabels(fontsize=15)
+            # ax.set_title('ROC curve')
+            ax.legend(loc='lower right', fontsize=LEGEND_SIZE)
+
+            line_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_pr.png', dpi=400)
+            line_plot.figure.savefig(f'{output_dir}/{model}_{task}_{add_on}_pr.svg', format='svg')
+            # plt.show()
+
+            line_plot.figure.clf()
 
     else: 
         # print(fpr_list)
+        
         color = COLOR_MAP[0]
         precision, recall, thresholds = binary_precision_recall_curve(probs, target)
-        baseline = len(target[target==1]) / len(target)
+        baseline = len(target[target==target_label]) / len(target)
         
         pr = precision
         re = recall
@@ -358,107 +473,124 @@ def get_pr_curve(probs, target, task):
         # ax.plot(re, pr)
         df = pd.DataFrame(data = {'re': re.cpu().numpy(), 'pr': pr.cpu().numpy()})
         line_plot = sns.lineplot(data=df, x='re', y='pr', label=f'{partial_auc:.3f}', legend='full', color=color)
-        
     
-        ax.plot([0,1], [baseline, baseline], linestyle='--', label=f'Baseline={baseline:.3f}', color=color)
+        ax.plot([0,1], [baseline, baseline], linestyle='--', color=color) #label=f'Baseline={baseline:.3f}', 
 
     ax.set_xlim([0,1])
     ax.set_ylim([0,1])
-    ax.set_xlabel('Recall', fontsize=18)
-    ax.set_ylabel('Precision', fontsize=18)
+    ax.set_xlabel('Precision', fontsize=AXIS_SIZE)
+    # ax.set_xlabel('')
+    ax.set_ylabel('Recall', fontsize=AXIS_SIZE)
     # ax.set_title('PR curve')
-    ax.legend(loc='lower right', fontsize=15)
+    ax.tick_params(axis='x', labelsize=25)
+    ax.tick_params(axis='y', labelsize=25)
+    ax.legend(loc='lower right', fontsize=LEGEND_SIZE)
 
     return line_plot
 
-def get_confusion_matrix(probs, target, task, threshold_csv_path, comment='patient', stage='test'): # threshold
-
-        
-        if task == 'norm_rest' or task == 'rej_rest' or task == 'rest_rej':
-
-            n_classes = 2 
-            ROC = torchmetrics.ROC(task='binary')
-        else: 
-            n_classes = 3
-            ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
-
-
-        # preds = torch.argmax(probs, dim=1)
-        # if self.n_classes <= 2:
-        #     probs = probs[:,1] 
-
-        # read threshold file
-        # threshold_csv_path = f'{self.loggers[0].log_dir}/val_thresholds.csv'
-        # if not Path(threshold_csv_path).is_file():
-        #     # thresh_dict = {'index': ['train', 'val'], 'columns': , 'data': [[0.5, 0.5], [0.5, 0.5]]}
-        #     thresh_df = pd.DataFrame({'slide': [0.5], 'patient': [0.5]})
-        #     thresh_df.to_csv(threshold_csv_path, index=False)
-
-        # thresh_df = pd.read_csv(threshold_csv_path)
-        # if stage != 'test':
-        #     if n_classes <= 2:
-        #         fpr_list, tpr_list, thresholds = ROC(probs, target)
-        #         optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(fpr_list, tpr_list, thresholds)
-        #         # print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
-        #         thresh_df.at[0, comment] =  optimal_threshold
-        #         thresh_df.to_csv(threshold_csv_path, index=False)
-        #     else: 
-        #         optimal_threshold = 0.5
-        # elif stage == 'test': 
-
-        if n_classes == 2:    
-            fpr_list, tpr_list, thresholds = ROC(probs, target)
-            optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(fpr_list, tpr_list, thresholds)
-        else:
-            optimal_threshold = 0.5
-        # optimal_threshold = thresh_df.at[0, comment]
+def get_confusion_matrix(probs, target, task, optimal_threshold, comment='patient', stage='test'): # threshold
 
-        print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
-            # optimal_threshold = 0.5 # manually change to val_optimal_threshold for testing
+    if type(probs) is np.ndarray:
+        probs = torch.from_numpy(probs)
+    if type(target) is np.ndarray:
+        target = torch.from_numpy(target)
 
-        # print(confmat)
-        # confmat = self.confusion_matrix(preds, target, threshold=optimal_threshold)
-        if n_classes == 2:
-            confmat = confusion_matrix(probs, target, task='binary', threshold=optimal_threshold)
-        elif n_classes > 2: 
-            confmat = confusion_matrix(probs, target, task='multiclass', num_classes=n_classes)
+    if task == 'norm_rest' or task == 'rej_rest' or task == 'rest_rej':
 
-        cm_labels = LABEL_MAP[task].values()
+        n_classes = 2 
+        ROC = torchmetrics.ROC(task='binary')
+    else: 
+        n_classes = 3
+        ROC = torchmetrics.ROC(task='multiclass', num_classes=n_classes)
 
-        # fig, ax = plt.subplots()
-        figsize=plt.rcParams.get('figure.figsize')
-        plt.figure(figsize=figsize)
 
-        # df_cm = pd.DataFrame(confmat.cpu().numpy(), index=range(self.n_classes), columns=range(self.n_classes))
-        df_cm = pd.DataFrame(confmat.cpu().numpy(), index=cm_labels, columns=cm_labels)
-        # fig_ = sns.heatmap(df_cm, annot=True, fmt='d', cmap='Spectral').get_figure()
-        # sns.set(font_scale=1.5)
-        sns.heatmap(df_cm, annot=True, fmt='d', cmap='Blues', cbar=False, annot_kws={'fontsize': 'x-large', 'multialignment':'center'})
+    # preds = torch.argmax(probs, dim=1)
+    # if self.n_classes <= 2:
+    #     probs = probs[:,1] 
 
-        plt.yticks(va='center')
-        plt.ylabel('True label')
-        plt.xlabel('Predicted label')
+    # read threshold file
+    # threshold_csv_path = f'{self.loggers[0].log_dir}/val_thresholds.csv'
+    # if not Path(threshold_csv_path).is_file():
+    #     # thresh_dict = {'index': ['train', 'val'], 'columns': , 'data': [[0.5, 0.5], [0.5, 0.5]]}
+    #     thresh_df = pd.DataFrame({'slide': [0.5], 'patient': [0.5]})
+    #     thresh_df.to_csv(threshold_csv_path, index=False)
+    # else:  
+    # thresh_df = pd.read_csv(threshold_csv_path, index_col=False)
+    # optimal_threshold = thresh_df['patient'].values[0]
+    # print(optimal_threshold)
+    # if stage != 'test':
+    #     if n_classes <= 2:
+    #         fpr_list, tpr_list, thresholds = ROC(probs, target)
+    #         optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(fpr_list, tpr_list, thresholds)
+    #         # print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
+    #         thresh_df.at[0, comment] =  optimal_threshold
+    #         thresh_df.to_csv(threshold_csv_path, index=False)
+    #     else: 
+    #         optimal_threshold = 0.5
+    # elif stage == 'test': 
+    # if n_classes > 2:
+    #     optimal_threshold=1/n_classes
+    # if n_classes == 2:    
+    #     optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(probs, target)
+
+        # fpr_list, tpr_list, thresholds = ROC(probs, target)
+        # optimal_fpr, optimal_tpr, optimal_threshold = get_optimal_operating_point(fpr_list, tpr_list, thresholds)
+    # else:
+    #     optimal_threshold = 0.5
+    # optimal_threshold = thresh_df.at[0, comment]
+
+    # print(f'Optimal Threshold {stage} {comment}: ', optimal_threshold)
+        # optimal_threshold = 0.5 # manually change to val_optimal_threshold for testing
+
+    # print(confmat)
+    # confmat = self.confusion_matrix(preds, target, threshold=optimal_threshold)
+    if n_classes == 2:
+        confmat = confusion_matrix(probs, target, task='binary', threshold=optimal_threshold)
+    elif n_classes > 2: 
+        confmat = confusion_matrix(probs, target, task='multiclass', num_classes=n_classes, threshold=optimal_threshold)
+
+    cm_labels = LABEL_MAP[task].values()
+
+    # fig, ax = plt.subplots()
+    figsize=plt.rcParams.get('figure.figsize')
+    plt.figure(figsize=(10, 10))
+
+    # df_cm = pd.DataFrame(confmat.cpu().numpy(), index=range(self.n_classes), columns=range(self.n_classes))
+    df_cm = pd.DataFrame(confmat.cpu().numpy(), index=cm_labels, columns=cm_labels)
+    print(df_cm)
+    # fig_ = sns.heatmap(df_cm, annot=True, fmt='d', cmap='Spectral').get_figure()
+    # sns.set(font_scale=1.5)
+    cm_plot = sns.heatmap(df_cm, annot=True, fmt='d', cmap='Blues', cbar=False, annot_kws={'fontsize': LEGEND_SIZE, 'multialignment':'center'}) #
+    cm_plot.set_xticklabels(cm_plot.get_xmajorticklabels(), fontsize = 30)
+    cm_plot.set_yticklabels(cm_plot.get_ymajorticklabels(), fontsize = 30)
+    # cm_plot.xaxis.tick_top()
+    # cm_plot.set_yticklabels(fontsize=30)
+    # sns.set(font_scale=1.3)
+
+    plt.yticks(va='center')
+    plt.ylabel('True', fontsize=AXIS_SIZE)
+    plt.xlabel('Prediction', fontsize=AXIS_SIZE)
 
 
-        
-        
-        
-        # cm_plot = 
-        # if stage == 'train':
-        #     self.loggers[0].experiment.add_figure(f'{stage}/Confusion matrix', cm_plot.figure, self.current_epoch)
-        #     if len(self.loggers) > 2:
-        #         self.loggers[2].log_image(key=f'{stage}/Confusion matrix', images=[cm_plot.figure], caption=[self.current_epoch])
-        #     # self.loggers[0].experiment.add_figure(f'{stage}/Confusion matrix', cm_plot.figure, self.current_epoch)
-        # else:
-        #     ax.set_title(f'{stage}_{comment}')
-        #     if comment: 
-        #         stage += f'_{comment}'
-        #     # fig_.savefig(f'{self.loggers[0].log_dir}/cm_{stage}.png', dpi=400)
-        #     cm_plot.figure.savefig(f'{self.loggers[0].log_dir}/{stage}_cm.png', dpi=400)
-
-        # # fig.clf()
-        # cm_plot.figure.clf()
-        return plt
+    
+    
+    
+    # cm_plot = 
+    # if stage == 'train':
+    #     self.loggers[0].experiment.add_figure(f'{stage}/Confusion matrix', cm_plot.figure, self.current_epoch)
+    #     if len(self.loggers) > 2:
+    #         self.loggers[2].log_image(key=f'{stage}/Confusion matrix', images=[cm_plot.figure], caption=[self.current_epoch])
+    #     # self.loggers[0].experiment.add_figure(f'{stage}/Confusion matrix', cm_plot.figure, self.current_epoch)
+    # else:
+    #     ax.set_title(f'{stage}_{comment}')
+    #     if comment: 
+    #         stage += f'_{comment}'
+    #     # fig_.savefig(f'{self.loggers[0].log_dir}/cm_{stage}.png', dpi=400)
+    #     cm_plot.figure.savefig(f'{self.loggers[0].log_dir}/{stage}_cm.png', dpi=400)
+
+    # # fig.clf()
+    # cm_plot.figure.clf()
+    return cm_plot, optimal_threshold
 
 
 if __name__ == '__main__':
diff --git a/code/visualize_feature.py b/code/visualize_feature.py
index ec3c3a8636ded49e55726541af48a794ce8117d6..09d4d6e00a18b28693f4ad3483a708f575d0144f 100644
--- a/code/visualize_feature.py
+++ b/code/visualize_feature.py
@@ -74,9 +74,13 @@ class Visualize():
 
         home = Path.cwd().parts[1]
 
+        # self.jpg_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/DEEPGRAFT_RU/BLOCKS'
+        # self.roi_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/DEEPGRAFT_RU/ROI'
+        # self.save_path = Path(f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/mil_model_features/')
         self.jpg_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/Aachen_Biopsy_Slides_extended/BLOCKS'
         self.roi_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/Aachen_Biopsy_Slides_extended/ROI'
         self.save_path = Path(f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/mil_model_features/')
+        # self.save_path = Path(f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/results_test/mil_model_features/')
         
 
         self.checkpoint = torch.load(checkpoint_path)
@@ -172,8 +176,6 @@ class Visualize():
             y = c[1]
             coords.append((int(x),int(y)))
 
-        
-
         for i, (x,y) in enumerate(coords):
             if x not in position_dict.keys():
                 position_dict[x] = [(y, i)]
@@ -199,6 +201,8 @@ class Visualize():
             tpk_df = pd.read_csv(tpk_csv_path)
             self.topk_dict[str(n)] = {'patients': list(tpk_df.head(5)['Patient']), 'labels': [n] * len(list(tpk_df.head(5)['Patient']))}
 
+    # def _rescale(self, ):
+
 
     def assemble(self, wsi_name, batch_coords, grayscale_cam, mil_grayscale_cam, input_h=224):
 
@@ -207,29 +211,49 @@ class Visualize():
         x_max = max([x[0] for x in coords])
         y_max = max([x[1] for x in coords])
 
-        mean_cam = torch.mean(mil_grayscale_cam, dim=2)
+
+        mean_cam = mil_grayscale_cam[:, :, 1].squeeze()
+        # mean_cam = mil_grayscale_cam
+        # mean_cam = torch.mean(mil_grayscale_cam, dim=2)
+        mean_cam -= torch.min(mean_cam)
+        mean_cam /= torch.max(mean_cam) #normalize
+
+
+        # print(mil_grayscale_cam)
         # print(mean_cam.shape)
         # print(mean_cam.shape)
-        percentage_shown = 0.4
-        topk = int(mean_cam.shape[1] * percentage_shown) #
-        print(topk)
-
-        _, topk_indices = torch.topk(mean_cam, topk, dim=1)
-        batch_coords = torch.index_select(batch_coords.squeeze(), 0, topk_indices[0])
-        grayscale_cam = torch.index_select(grayscale_cam.squeeze(), 0, topk_indices[0])
+        # print(mean_cam.shape)
+        percentage_shown = 0.4 #0.4 for all results
+        # topk = int(mean_cam.shape[0]) #
+        topk = int(mean_cam.shape[0] * percentage_shown) #
+        # print(topk)
+
+        _, topk_indices = torch.topk(mean_cam, topk, dim=0)
+        # print(topk_indices)
+        # print(len(topk_indices))
+        batch_coords = torch.index_select(batch_coords.squeeze(), 0, topk_indices)
+        grayscale_cam = torch.index_select(grayscale_cam.squeeze(), 0, topk_indices)
+        # print(mean_cam.shape)
+        # print(grayscale_cam.shape)
+        # print(mean_cam[0])
+        # print(grayscale_cam[0, :, :])
+        # grayscale_cam = mean_cam@grayscale_cam
 
         feature_cam = torch.zeros([(y_max+1)*224, (x_max+1)*224])
-        # print('batch_coords:', batch_coords.shape)
+        print('batch_coords:', batch_coords.shape)
         # print(coords.shape)
+        # for i,( c, img, w) in enumerate(zip(batch_coords.squeeze(0), grayscale_cam, mean_cam)):
         for i,( c, img) in enumerate(zip(batch_coords.squeeze(0), grayscale_cam)):
             c = c.squeeze()
+            
             x = c[0].item()
             y = c[1].item()
             # print(x, y)
             
             # print(img.shape)
             # if i in topk_indices:
-            feature_cam[y*224:y*224+224, x*224:x*224+224] = img 
+            # feature_cam[y*224:y*224+224, x*224:x*224+224] = img * w
+            feature_cam[y*224:y*224+224, x*224:x*224+224] = img
         # feature_cam = (feature_cam - feature_cam.min())/(feature_cam.max()-feature_cam.min())
         wsi = torch.ones([(y_max+1)*224, (x_max+1)*224, 3])
         roi = np.zeros([(y_max+1)*224, (x_max+1)*224])
@@ -254,8 +278,6 @@ class Visualize():
                     img_cam = img_cam.convert('RGB')
                     img_cam.save(f'{self.output_path}/tiles/{wsi_name}/{wsi_name}_({co[0]}-{co[1]})_gradcam.jpg')
 
-
-
                     roi_path =  Path(self.roi_dir) / wsi_name / f'{wsi_name}_({co[0]}-{co[1]}).png'
                     img = np.asarray(Image.open(roi_path)).astype(np.uint8)
                     img = img / 255.0
@@ -362,7 +384,7 @@ class Visualize():
         print('Save GradCAM overlay.')
         img = Image.fromarray(wsi_cam)
         img = img.convert('RGB')
-        img.save(f'{self.output_path}/{wsi_name}_gradcam.jpg')
+        img.save(f'{self.output_path}/{wsi_name}_mil_gradcam.jpg')
 
 
     def run(self, target_label):
@@ -388,12 +410,14 @@ class Visualize():
         # print(slides)
         self.output_path = self.output_path / str(target_label)
         self.output_path.mkdir(parents=True, exist_ok=True)
+        # print(self.output_path)
         slides_done = [x.stem.rsplit('_', 1)[0] for x in list(self.output_path.iterdir()) if Path(x).suffix == '.jpg']
-        slides_done += ['29.61s/it]Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcLAXK110_01_007_PAS']
+        print(slides_done)
+        # slides_done += ['Aachen_KiBiDatabase_KiBiAcOSNX750_01_006_HE', 'Aachen_KiBiDatabase_KiBiAcOSNX750_01_008_Jones', 'Aachen_KiBiDatabase_KiBiAcOSNX750_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcUAYM660_01_006_HE', 'Aachen_KiBiDatabase_KiBiAcUAYM660_01_008_Jones', 'Aachen_KiBiDatabase_KiBiAcUAYM660_01_014_PAS']
         # slides_done += ['Aachen_KiBiDatabase_KiBiAcZXRC970_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcZXRC970_01_006_HE', 'Aachen_KiBiDatabase_KiBiAcSVXX412_01_006_HE', 'Aachen_KiBiDatabase_KiBiAcUAYM660_01_008_Jones']
         # slides_done += ['Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcLAXK110_01_007_PAS', 'Aachen_KiBiDatabase_KiBiAcLAXK110_01_008_Jones']
         # slides_done += ['Aachen_KiBiDatabase_KiBiAcFLGQ191_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcFLGQ191_01_004_PAS', 'Aachen_KiBiDatabase_KiBiAcFLGQ191_01_008_Jones', ]
-        # slides_done += ['Aachen_KiBiDatabase_KiBiAcLAXK110_01_007_PAS', 'Aachen_KiBiDatabase_KiBiAcLAXK110_01_008_Jones', 'Aachen_KiBiDatabase_KiBiAcZXRC970_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS']
+        slides_done += ['Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS'] #, 'Aachen_KiBiDatabase_KiBiAcLAXK110_01_008_Jones', 'Aachen_KiBiDatabase_KiBiAcZXRC970_01_018_PAS', 'Aachen_KiBiDatabase_KiBiAcDKIK860_01_018_PAS']
         slides = [s for s in slides if s not in slides_done]
 
         try:
@@ -418,6 +442,8 @@ class Visualize():
 
         c = 0
         for item in tqdm(dl):
+            # if c >10:
+            #     break
 
             bag, label, (name, batch_coords, patient) = item
             
@@ -426,64 +452,61 @@ class Visualize():
 
             slide_name = name[0]
             print(slide_name)
+            print(bag.shape)
             # if slide_name != 'Aachen_KiBiDatabase_KiBiAcRLKM530_01_006_HE':
             # #     continue
             # # else:
             # if slide_name in self.slides_done:
             #     continue
+            # if bag.shape[1] > 200:
+                
+            #     temp = []
+            #     size_remaining = bag.shape[1]
+            #     i = 1
+            #     while size_remaining // 200 != 0:
+            #     # for i in range(bag.shape[1]//200 + 1):[[]]
+            #         sub_bag = bag[:, (i-1)*200:i*200, : , :, :].flloat().squeeze(0)
+            #         i += 1
+            #         size_remaining -= 200
+            #         temp.append(sub_bag)
+            #     else: 
+            #         sub_bag = bag[:, size_remaining%200: , : , :, :].flloat().squeeze(0)
+            #         temp.append(sub_bag)
+                
 
 
-            bag = bag.float().squeeze(0).to(self.device)
-            # features = model[0](bag.squeeze())
-            # with torch.no_grad():
-            #     features = feature_model(bag.squeeze())
-                # scores = model[1](features)
-            # print(scores)
-            instance_count = bag.size(0)
-            # bag = bag.detach()    
-            # features = features.detach()
-            #     
-                # with torch.cuda.amp.autocast():
-                #     pred = model(bag)
-                # print(pred.shape)
-            # target_layers = [feature_model.layer4[-1]]
-            # with GradCAM(model=feature_model, target_layers=target_layers, use_cuda=True) as cam:
-                # cam    = self._get_cam_object('Resnet50', model)
-            grayscale_cam = feature_cam(input_tensor=bag.detach(), targets=cam_target)
-            grayscale_cam = torch.Tensor(grayscale_cam)
-            
-            with torch.no_grad():
-                features = feature_model(bag.squeeze())
 
-            
+            half_size = int(bag.shape[1]/2)
+            half_bag_1 = bag[:,:half_size, :, :, :].float().squeeze(0)
+            half_bag_2 = bag[:,half_size:, :, :, :].float().squeeze(0)
 
 
-            # mil_cam = self._get_cam_object(self.model_name, model[1])
 
-            # mil_grayscale_cam = mil_cam(input_tensor=features.unsqueeze(0), targets=cam_target)
-            # mil_grayscale_cam = torch.Tensor(mil_grayscale_cam)[:instance_count, :]
-            # target_layers = [mil_model.norm]
-            # cam = GradCAM(model=model, target_layers = target_layers, use_cuda=True, reshape_transform=self._reshape_transform)
-            # with GradCAM(model=mil_model, target_layers = target_layers, use_cuda=True, reshape_transform=self._reshape_transform) as cam:
+            # bag = bag.float().squeeze(0) #.to(self.device)
+            instance_count = bag.size(0)
+
+            grayscale_cam_1 = feature_cam(input_tensor=half_bag_1.detach(), targets=cam_target)
+            grayscale_cam_2 = feature_cam(input_tensor=half_bag_2.detach(), targets=cam_target)
+
+            # print(grayscale_cam_1.shape)
+            # print(grayscale_cam_2.shape)
+            grayscale_cam = torch.cat((torch.Tensor(grayscale_cam_1), torch.Tensor(grayscale_cam_2)))
+            # grayscale_cam = torch.Tensor(grayscale_cam)
+            # print(grayscale_cam.shape)
+            
+            with torch.no_grad():
+                features = feature_model(bag.squeeze().to(self.device))
 
             mil_grayscale_cam = mil_cam(input_tensor=features.unsqueeze(0), targets=cam_target)
             mil_grayscale_cam = torch.Tensor(mil_grayscale_cam)[:instance_count, :]
+            # mil_grayscale_cam = mil_grayscale_cam[:, :, 1].squeeze()
+
             
-            # del mil_cam
-            # del bag
-            # del features
-            # torch.cuda.empty_cache()
-            
-            # print(mil_grayscale_cam.shape)
-           
-                # print(grayscale_cam.shape)
-            
-                # # bag = bag.detach()
-                # # print(target_label)
-                # # self._save_attention_map(slide_name, batch_coords, grayscale_cam)
-                # print(grayscale_cam.max())
-                # print(grayscale_cam.min())
             self.assemble(slide_name, batch_coords, grayscale_cam, mil_grayscale_cam)
+            self._save_attention_map(slide_name, batch_coords, mil_grayscale_cam)
+
+
+            # c+= 1
 
     # for t in test_dataset:
 
@@ -545,6 +568,8 @@ if __name__ == '__main__':
 
     target_label = args.target_label
     print(task)
+    print(model_paths)
+    print(cfg.log_path)
     
     # for target_label in range(args.total_classes):
     visualizer = Visualize(checkpoint_path=model_paths[0], task=cfg.task)
diff --git a/code/visualize_mil.py b/code/visualize_mil.py
index 822df6560396e3065bb6475633a4a0b29a99dfd1..6078ad6a1c3a49df277820433521583f8c825844 100644
--- a/code/visualize_mil.py
+++ b/code/visualize_mil.py
@@ -76,7 +76,7 @@ class Visualize():
 
         self.jpg_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/Aachen_Biopsy_Slides_extended/TEST'
         self.roi_dir = f'/{home}/ylan/data/DeepGraft/224_256uM_annotated/Aachen_Biopsy_Slides_extended/ROI'
-        self.save_path = Path(f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/mil_model_extended/')
+        self.save_path = Path(f'/{home}/ylan/workspace/TransMIL-DeepGraft/test/mil_model_test_2/')
         
 
         self.checkpoint = torch.load(checkpoint_path)
@@ -110,6 +110,7 @@ class Visualize():
 
         # self.label_path = new_path
         self.data_root = self.hparams['data']['data_dir']
+        print(self.data_root)
 
         self.mil_model = None
         self.feat_model = None
@@ -219,9 +220,11 @@ class Visualize():
         #----------------------------------------------
         # Get mask from gradcam
         #----------------------------------------------
-        mil_attention_map = mil_grayscale_cam[:, :, 1].squeeze()
-        mil_attention_map = (mil_attention_map-mil_attention_map.min()) / (mil_attention_map.max() - mil_attention_map.min())
+        # mil_attention_map = mil_grayscale_cam[:, :, 1].squeeze()
+        mil_attention_map = mil_grayscale_cam.squeeze()
+        mil_attention_map = (mil_attention_map-mil_attention_map.min()) / (mil_attention_map.max() - mil_attention_map.min()) #normalize to 0:1
         mask = torch.zeros(( int(W/input_h), int(H/input_h)))
+        
         for i, (x,y) in enumerate(coords):
             mask[y][x] = mil_attention_map[i]
         mask = mask.unsqueeze(0).unsqueeze(0)
@@ -229,8 +232,8 @@ class Visualize():
         mask = F.interpolate(mask, (W,H), mode='bilinear')
         mask = mask.squeeze(0).permute(1,2,0)
 
-        mask = (mask - mask.min())/(mask.max()-mask.min())
-        mask = mask.numpy()
+        # mask = (mask - mask.min())/(mask.max()-mask.min()) # normalize again..?
+        mask = mask.numpy(force=True)
         # mask = gaussian_filter(mask, sigma=15)
 
         wsi_cam = show_cam_on_image(wsi.numpy(), mask, use_rgb=True, image_weight=0.6, colormap=cv2.COLORMAP_JET)
@@ -267,9 +270,9 @@ class Visualize():
         self.output_path = self.output_path / str(target_label)
         self.output_path.mkdir(parents=True, exist_ok=True)
         skip_slides = [x.stem.rsplit('_', 1)[0] for x in list(self.output_path.iterdir()) if Path(x).suffix == '.jpg']
-        skip_slides += ['Aachen_KiBiDatabase_KiBiAcDKIK860_01_006_HE']
+        # skip_slides += ['Aachen_KiBiDatabase_KiBiAcDKIK860_01_006_HE']
         slides = [s for s in slides if s not in skip_slides]
-
+        print(slides)
         try:
             len(slides) != 0
         except:
@@ -278,7 +281,10 @@ class Visualize():
         # print(slides)
 
         test_dataset = JPGMILDataloader(file_path=self.data_root, label_path=self.label_path, mode='test', cache=False, n_classes=self.n_classes, model=self.model_name, slides=slides)
+        
+        print(len(test_dataset))
         dl = DataLoader(test_dataset, batch_size=1, num_workers=4, pin_memory=True)
+        print(len(dl))
 
         for item in tqdm(dl):
             
@@ -288,24 +294,51 @@ class Visualize():
 
             slide_name = name[0]
             print(slide_name)
-            if slide_name != 'Aachen_KiBiDatabase_KiBiAcRLKM530_01_006_HE':
+            # if slide_name != 'Aachen_KiBiDatabase_KiBiAcRLKM530_01_006_HE':
             #     continue
             # else:
             
 
-                bag = bag.float().to(self.device)
-                with torch.cuda.amp.autocast():
-                    features = feature_model(bag.squeeze())
-                instance_count = bag.size(0)
-                bag = bag.detach()
-                cam_target = [ClassifierOutputTarget(target_label)]
-                
-                mil_grayscale_cam = self.cam(input_tensor=features.unsqueeze(0), targets=cam_target)
-                mil_grayscale_cam = torch.Tensor(mil_grayscale_cam)[:instance_count, :]
-                features = features.detach()
-                bag = bag.detach()
-                # print(target_label)
-                self._save_attention_map(slide_name, batch_coords, mil_grayscale_cam)
+            bag = bag.float().to(self.device)
+            # with torch.cuda.amp.autocast():
+            with torch.no_grad():
+                features = feature_model(bag.squeeze())
+
+
+
+            size = features.shape[0]
+            # print(features.shape)
+
+            x, attn = mil_model(features.unsqueeze(0), return_attn=True)
+
+            # print(attn)
+
+            # print(attn.shape)
+            cls_attention = attn[:,:, 0, :size]
+            # print(cls_attention)
+            values, indices = torch.max(cls_attention, 1)
+            mean = values.mean()
+            zeros = torch.zeros(values.shape).cuda()
+            filtered = torch.where(values > mean, values, zeros)
+
+            # print(filtered.shape)
+
+
+
+            instance_count = bag.size(0)
+            bag = bag.detach()
+            cam_target = [ClassifierOutputTarget(target_label)]
+            
+            mil_grayscale_cam = self.cam(input_tensor=features.unsqueeze(0), targets=cam_target)
+            mil_grayscale_cam = torch.Tensor(mil_grayscale_cam)[:instance_count, :]
+            mil_grayscale_cam = mil_grayscale_cam[:, :, 1].squeeze()
+
+            # print(mil_grayscale_cam.shape)
+            features = features.detach()
+            bag = bag.detach()
+            # print(target_label)
+            self._save_attention_map(slide_name, batch_coords, mil_grayscale_cam)
+            # self._save_attention_map(slide_name, batch_coords, filtered)
 
     # for t in test_dataset:
 
diff --git a/paper_structure.md b/paper_structure.md
index c6f76cc660c3c61fa0398b9b0a0841a510155a79..54732a8f79b8982f93826dfd473f76d02e404cc7 100644
--- a/paper_structure.md
+++ b/paper_structure.md
@@ -2,6 +2,8 @@
 
 ## Abstract
 
+
+
 ## Introduction
 
 Why do we do this