diff --git a/.gitignore b/.gitignore index 65646b6..326815b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,15 @@ data/*/* +predictions/* +!predictions/.keep + +checkpoints/* +!checkpoints/.keep + +runs/* +!runs/.keep + data/train_x/* !data/train_x/.keep data/train_y/* diff --git a/ISIC_0000003.jpg b/ISIC_0000003.jpg deleted file mode 100644 index cc95799..0000000 Binary files a/ISIC_0000003.jpg and /dev/null differ diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index 4fa49fe..0000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 24, - "id": "e90dc848-4d65-4881-b374-0dc8ac5c555d", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_values([8, 13, 1, 16, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 210, 1, 17, 15, 1, 1, 15, 1, 1, 1, 1, 7, 1, 25, 15, 1])\n" - ] - } - ], - "source": [ - "import os\n", - "from collections import Counter\n", - "import matplotlib.pyplot as plt\n", - "from PIL import Image\n", - "\n", - "cache_imgs = []\n", - "dir_name = \"data/test_x/\"\n", - "for filename in sorted(os.listdir(dir_name)):\n", - " if filename.endswith(\".jpg\"): \n", - " img = Image.open(dir_name + filename)\n", - " size = img.size\n", - " cache_imgs.append(size)\n", - "\n", - "\n", - "cache_masks = []\n", - "dir_name = \"data/test_y/\"\n", - "\n", - "for filename in sorted(os.listdir(dir_name)):\n", - " if filename.endswith(\".png\"): \n", - " img = Image.open(dir_name + filename)\n", - " size = img.size\n", - " cache_masks.append(size)\n", - "\n", - "plt.plot(cache_imgs)\n", - "plt.show()\n", - " \n", - "\n", - "#print(\"Unique Image sizes: \" + str(sorted(cache_imgs)))\n", - "print(Counter(cache_imgs).values())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e8392a9f-6332-486d-8b14-25d15c5704e6", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.8.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/output/.keep b/checkpoints/.keep similarity index 100% rename from output/.keep rename to checkpoints/.keep diff --git a/eval.py b/eval.py index cbff39a..4c1a88d 100644 --- a/eval.py +++ b/eval.py @@ -1,7 +1,11 @@ import os import torch +from torch.functional import Tensor import torch.nn.functional as F from tqdm import tqdm +import ntpath + +import numpy as np from dice_loss import dice_coeff @@ -20,6 +24,10 @@ def eval_net(net, loader, device, **kwargs): else: desc = 'Validation round' + loss = 0 + + booleanTOT = 0 + with tqdm(total=n_val, desc=desc, unit='batch', leave=False) as pbar: for batch in loader: imgs, true_masks, id = batch['image'], batch['mask'], batch['id'][0] @@ -31,17 +39,58 @@ def eval_net(net, loader, device, **kwargs): pred = torch.sigmoid(mask_pred) pred = (pred > 0.5).float() - tot += dice_coeff(pred, true_masks).item() + tot += dice_coeff(pred, true_masks) if 'output_directory' in kwargs: # save output save_prediction(pred, id, **kwargs) + if 'criterion' in kwargs: + loss += kwargs['criterion'](pred, true_masks).item() + + # print(f'') + pbar.update() - net.train() + if 'criterion' in kwargs: + return tot/n_val, loss/n_val + return tot / n_val def save_prediction(pred, id, **kwargs): - output_path = os.path.join(kwargs['output_directory'], f'{id.strip(".jpg")}.png') + outputDir = os.path.join(kwargs['output_directory'] + '/' + ntpath.basename(kwargs['model_path'])) + if not os.path.exists(outputDir): + os.makedirs(outputDir) + + pred = pred[:,:,:,:] + output_path = os.path.join(outputDir, f'{id.strip(".jpg")}.png') save_image(pred, output_path) - \ No newline at end of file + + +# def DSC(prediction: Tensor, ground_truth: Tensor) -> float: + +# #assert prediction.shape() == ground_truth.shape() + +# # intersection = 1,1 ; 0,0 +# # intersection = |set| - |xor| +# intersection = prediction.numel() - torch.sum(torch.logical_xor(prediction, ground_truth)) + +# numerator = 2 * intersection +# denominator = prediction.numel() + ground_truth.numel() + +# return numerator / denominator + +# def booleanDSC(prediction, ground_truth) -> float: + +# # false_positives = 1,1 +# true_positives = sum(torch.logical_and(prediction, ground_truth)) + +# # false_positives = 1,0 +# # false_negative = 0,1 +# # XOR = false_positives + false_negatives +# xor = torch.sum(torch.logical_xor(prediction, ground_truth)) + +# numerator = 2 * true_positives +# denominator = 2 * true_positives + xor + + +# return numerator / denominator \ No newline at end of file diff --git a/eval_on_test.py b/eval_on_test.py index 20f2f8d..d13e121 100644 --- a/eval_on_test.py +++ b/eval_on_test.py @@ -48,6 +48,7 @@ def parse_args() -> argparse.Namespace: '--scale', type=float, default='.5', + dest='scale', help='Scaling of images') parser.add_argument('-u', @@ -61,8 +62,9 @@ def parse_args() -> argparse.Namespace: def run_predictions(args): device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') dataset = MelanomiaDataset(args.image_directory, - args.mask_directory) - loader = DataLoader(dataset, num_workers=8, pin_memory=True) + args.mask_directory, + args.scale) + loader = DataLoader(dataset, num_workers=1, pin_memory=True) net = Unet(addPadding=args.padding) net.load_state_dict( @@ -70,7 +72,7 @@ def run_predictions(args): ) net.to(device=device) - eval_net(net, loader, device, desc = 'Testing round', **vars(args)) + print(eval_net(net, loader, device, desc = 'Testing round', **vars(args))) if __name__ == '__main__': diff --git a/main.py b/main.py index 93f8e8f..40dac7e 100644 --- a/main.py +++ b/main.py @@ -10,29 +10,19 @@ from tqdm import tqdm from eval import eval_net - from unet import Unet from torch.utils.tensorboard import SummaryWriter from torch.utils.data import DataLoader, random_split from melanomia_dataset import MelanomiaDataset - from datetime import datetime -from helpers import BasicDataset - -from PIL import Image -from image_processor import preprocess_image -from torchvision.utils import save_image - - dir_img = 'data/ISBI2016_ISIC_Part1_Training_Data/' dir_mask = 'data/ISBI2016_ISIC_Part1_Training_GroundTruth/' -dir_checkpoint = 'checkpoints/' def train_net(net, @@ -42,18 +32,24 @@ def train_net(net, lr=0.001, val_percent=0.1, save_cp=True, - img_scale=0.5): + img_scale=0.5, + time='', + padding=0): + + + dir_checkpoint = f'checkpoints/{time}LR_{lr}_BS_{batch_size}_SCALE_{img_scale}_PADDING{padding}' dataset = MelanomiaDataset(dir_img, dir_mask, img_scale) - #dataset = BasicDataset(dir_img,dir_mask,img_scale,mask_suffix="_Segmentation") n_val = int(len(dataset) * val_percent) n_train = len(dataset) - n_val + train, val = random_split(dataset, [n_train, n_val]) - train_loader = DataLoader(train, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True) - val_loader = DataLoader(val, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True, drop_last=True) + train_loader = DataLoader(train, batch_size=batch_size, shuffle=True, num_workers=1, pin_memory=True) + val_loader = DataLoader(val, batch_size=batch_size, shuffle=False, num_workers=1, pin_memory=True, drop_last=True) + + writer = SummaryWriter(comment=f'LR_{lr}_BS_{batch_size}_SCALE_{img_scale}_PADDING{padding}') - writer = SummaryWriter(comment=f'LR_{lr}_BS_{batch_size}_SCALE_{img_scale}') global_step = 0 logging.info(f'''Starting training: @@ -72,62 +68,56 @@ def train_net(net, criterion = nn.BCEWithLogitsLoss() - # Alternative loss, better iwht more classes - #criterion = nn.CrossEntropyLoss() - - - for epoch in range(epochs): net.train() epoch_loss = 0 with tqdm(total=n_train, desc=f'Epoch {epoch + 1}/{epochs}', unit='img') as pbar: for batch in train_loader: - imgs = batch['image'] - true_masks = batch['mask'] - assert imgs.shape[1] == net.n_channels, \ - f'Network has been defined with {net.n_channels} input channels, ' \ - f'but loaded images have {imgs.shape[1]} channels. Please check that ' \ - 'the images are loaded correctly.' + image_batch = batch['image'].to(device=device, dtype=torch.float32) + mask_batch = batch['mask'].to(device=device, dtype=torch.float32) - imgs = imgs.to(device=device, dtype=torch.float32) - mask_type = torch.float32 - true_masks = true_masks.to(device=device, dtype=mask_type) + predictions = net(image_batch) - masks_pred = net(imgs) - - loss = criterion(masks_pred, true_masks) + loss = criterion(predictions, mask_batch) epoch_loss += loss.item() - writer.add_scalar('Loss/train', loss.item(), global_step) - pbar.set_postfix(**{'loss (batch)': loss.item()}) + writer.add_scalar('Loss/train', loss.item(), global_step) + pbar.set_postfix(**{'loss (epoch)': epoch_loss}) + # training step optimizer.zero_grad() loss.backward() nn.utils.clip_grad_value_(net.parameters(), 0.1) optimizer.step() - pbar.update(imgs.shape[0]) + pbar.update(image_batch.shape[0]) + global_step += 1 + if global_step % (n_train // (10 * batch_size)) == 0: for tag, value in net.named_parameters(): tag = tag.replace('.', '/') writer.add_histogram('weights/' + tag, value.data.cpu().numpy(), global_step) writer.add_histogram('grads/' + tag, value.grad.data.cpu().numpy(), global_step) - val_score = eval_net(net, val_loader, device) - scheduler.step(val_score) - writer.add_scalar('learning_rate', optimizer.param_groups[0]['lr'], global_step) - - logging.info('Validation Dice Coeff: {}'.format(val_score)) - writer.add_scalar('Dice/test', val_score, global_step) - writer.add_images('images', imgs, global_step) - writer.add_images('masks/true', true_masks, global_step) - writer.add_images('masks/pred', torch.sigmoid(masks_pred) > 0.5, global_step) + writer.add_scalar('learning_rate', optimizer.param_groups[0]['lr'], global_step) + writer.add_images('images', image_batch, global_step) + writer.add_images('masks/true', mask_batch, global_step) + writer.add_images('masks/pred', torch.sigmoid(predictions) > 0.5, global_step) if torch.cuda.is_available(): torch.cuda.empty_cache() + + # Write the total loss and eval score. + val_dice_score, val_loss = eval_net(net, val_loader, device, criterion = criterion) + + writer.add_scalar('Loss/Train Epoch', epoch_loss/n_train, epoch + 1) + writer.add_scalar('Loss/Validation Epoch', val_loss, epoch + 1) + writer.add_scalar('Dice/Validation', val_dice_score, epoch + 1) + + scheduler.step(epoch_loss) if save_cp: try: os.mkdir(dir_checkpoint) @@ -155,7 +145,7 @@ def get_args(): help='Downscaling factor of the images') parser.add_argument('-v', '--validation', dest='val', type=float, default=10.0, help='Percent of the data that is used as validation (0-100)') - parser.add_argument('-p', '--padding', dest='padding', type=bool, default=True, + parser.add_argument('-p', '--padding', dest='padding', type=int, default=1, help='Add padding in the convolutions') return parser.parse_args() @@ -196,7 +186,8 @@ def get_args(): lr=args.lr, device=device, img_scale=args.scale, - val_percent=args.val / 100) + val_percent=args.val / 100, + time = current_time) except KeyboardInterrupt: torch.save(net.state_dict(), 'INTERRUPTED.pth') logging.info('Saved interrupt') diff --git a/melanomia_dataset.py b/melanomia_dataset.py index 98a5350..f62392b 100644 --- a/melanomia_dataset.py +++ b/melanomia_dataset.py @@ -59,6 +59,9 @@ def scale_file_(self, image: Image, mask: Image) -> Tuple[Tensor, Tensor]: width, height = image.size width, height = int(width*self.scale), int(height*self.scale) + # width = 224 + # height = 224 + image = image.resize((width, height)) mask = mask.resize((width, height)) image, mask = np.array(image), np.array(mask) diff --git a/plots/data_points.txt b/plots/data_points.txt new file mode 100644 index 0000000..02588da --- /dev/null +++ b/plots/data_points.txt @@ -0,0 +1,33 @@ +LR: 0.00015 Scale: 0.42 +0.5328661203384399 +0.6925103664398193 +0.6969958543777466 +2.6342222625430622e-08 +0.6203827261924744 +0.7055454850196838 +0.6871829032897949 +0.7076613903045654 +0.7101237177848816 +0.6999033689498901 +LR: 0.0003 Scale: 0.42 +0.5149078965187073 +3.7212257719687614e-09 +0.5762770771980286 +0.647400975227356 +0.668533444404602 +0.48211535811424255 +0.6727656722068787 +0.669792890548706 +0.6599106192588806 +0.3272610306739807 +LR: 0.0001 Scale: 0.42 +0.543988823890686 +0.6076177954673767 +0.4291623532772064 +0.47379541397094727 +0.5646342635154724 +0.597223162651062 +0.5686771869659424 +0.6129987239837646 +0.5954622030258179 +0.6290056705474854 \ No newline at end of file diff --git a/plots/plotter.py b/plots/plotter.py new file mode 100644 index 0000000..c16c66d --- /dev/null +++ b/plots/plotter.py @@ -0,0 +1,32 @@ +from typing import Dict, List +from matplotlib import pyplot as plt + + +def get_data_points(text_file: str) -> List[Dict]: + data_points = [] + + with open(text_file) as data_file: + for _ in range(3): + dice_scores = [] + name = data_file.readline().strip('\n') + + for i in range(10): + dice_scores.append(float(data_file.readline())) + + data_points.append({'Name' : name, "Dice Scores" : dice_scores}) + + + return data_points + + +if __name__ == '__main__': + results = get_data_points('data_points.txt') + + for result in results: + plt.plot([epoch for epoch in range(1, 11)], result['Dice Scores'], label = result['Name']) + + plt.title('Dice Score - Validation Data') + plt.xlabel('Epoch') + plt.ylabel('Dice Score') + plt.legend() + plt.show() \ No newline at end of file diff --git a/predictions/.keep b/predictions/.keep new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9b8f876..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -numpy -matplotlib -scikit-learn -torch -jupyterlab diff --git a/runOvernight.py b/runOvernight.py new file mode 100644 index 0000000..e69de29 diff --git a/runs/.keep b/runs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/unet.py b/unet.py index b1e263c..3ac75a9 100644 --- a/unet.py +++ b/unet.py @@ -115,19 +115,4 @@ def forward(self, img): y_final = F.interpolate(y_final, output_size) - return y_final - - -if __name__ == "__main__": - # Tensor shape = (1, #filters, pxWidth, pxHeight) - - img = torch.rand((1, 1, 1022,676)) - - img_1 = Image.open("ISIC_0000003.jpg") - img_1 = preprocess_image(img_1, scale=1) - - model = Unet() - res = model(img_1) - - print(img_1.size()) - print(res.size()) + return y_final \ No newline at end of file