diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4083cc55b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.venv +.DS_Store \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..03d228cd2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,5 @@ +[submodule "lesion_detection_larsmoan/yolov7"] + path = lesion_detection_larsmoan/yolov7 + url = https://github.com/WongKinYiu/yolov7 + + diff --git a/lesion_detection_larsmoan/.gitignore b/lesion_detection_larsmoan/.gitignore new file mode 100644 index 000000000..323c29de3 --- /dev/null +++ b/lesion_detection_larsmoan/.gitignore @@ -0,0 +1,17 @@ +data/ +__pycache__ +out.txt +err.txt +err_p6_p100.txt +out_p6_p100.txt + +runs/ +wandb + +.DS_Store +.venv +.env +*.pt + +err_test.txt +out_test.txt \ No newline at end of file diff --git a/lesion_detection_larsmoan/README.md b/lesion_detection_larsmoan/README.md new file mode 100644 index 000000000..139e17d9f --- /dev/null +++ b/lesion_detection_larsmoan/README.md @@ -0,0 +1,259 @@ +## Lesion detection and classification using YOLOV7 on the ISIC2017 dataset + +### Table of Contents +- [Lesion detection and classification using YOLOV7 on the ISIC2017 dataset](#lesion-detection-and-classification-using-yolov7-on-the-isic2017-dataset) + - [Table of Contents](#table-of-contents) + - [Installation](#installation) +- [Dataset](#dataset) + - [Overview](#overview) + - [Preprocessing](#preprocessing) + - [Usage](#usage) + - [Model Architecture: open source YOLOV7 Model](#model-architecture-open-source-yolov7-model) + - [Core ideas used in the YOLOV1 paper:](#core-ideas-used-in-the-yolov1-paper) + - [Output](#output) + - [Training](#training) + - [Training discussion](#training-discussion) + - [Results](#results) + - [Confusion Matrix](#confusion-matrix) + - [F1 - curve](#f1---curve) + - [Precision - Recall curve](#precision---recall-curve) + - [Results discussion](#results-discussion) + - [Downsampling](#downsampling) + - [Anchor boxes misaligned](#anchor-boxes-misaligned) + - [Example outputs](#example-outputs) + + +### Installation +- Prerequisites: python=3.10.12 && cuda=11.7 + +**A GPU cluster is used for this project, more specifically rangpur @ UQ. Therefore a lot of the training and inference scripts are based on slurm jobs. If needed this can easily be converted to run locally.** + +``` +git clone git@github.com:larsmoan/PatternAnalysis-2023.git +git submodule init +git submodule update +pip install -r requirements.txt +``` + +## Dataset +**Source**: [ISIC 2017 Dataset](https://challenge.isic-archive.com/data/#2017) + +### Overview +Each image comes with corresponding label and segmentation file highlighting the lesion. +- **Training Set**: + - 2000 images. + +- **Validation Set**: + - 600 images. + +- **Test Set**: + - 150 images. + +**Lesion Classes**: +- `Melanoma` +- `Seborrheic Keratosis` +- `Nevi / Uknown`: Technically known as a benign skin lesion. Commonly referred to as a mole. + + +### Preprocessing +Given that the dataset provides segmentation files, there's a need for preprocessing to convert these labels into YOLO bounding box labels. + +Steps include: +1. Identify the maximum and minimum coordinates within the segmentation area. +2. Fit a bounding box around this region. +3. Assign the class based on the label provided in the associated CSV file. + +More information can be found in the file: [dataset_utils.py](./dataset_utils.py) + +The dataset itself also needs to be refactored a bit to work with YOLOV7, therefore the structure is changed to the following: +``` +dataset/ +│ +├── train/ +│ ├── img_1.jpg +│ ├── ... +│ ├── img_n.jpg +│ ├── img_1.txt +│ ├── ... +│ └── img_n.txt +│ +├── val/ +│ ├── img_1.jpg +│ ├── ... +│ ├── img_n.jpg +│ ├── img_1.txt +│ ├── ... +│ └── img_n.txt +│ +└── test/ + ├── img_1.jpg + ├── ... + ├── img_n.jpg + ├── img_1.txt + ├── ... + └── img_n.txt +``` + +The prepocessed dataset can be downloaded from this link: +https://drive.google.com/uc?id=1YI3pwanX35i7NCIxKnfXBozXiyQZcGbL or from [dataset_utils.py](./dataset_utils.py) + + + +### Usage +- Download the dataset and pretrained yolov7 weights: + ``` + python dataset_utils.py + ``` +- Train the model: + Using rangpur cluster: + ``` + sbatch run_custom_train.sh + ``` + Or using Google Colab: + [isic_train.ipynb](./isic_train.ipynb) +- Run inference on testset: + ``` + sbatch run_test.sh + ``` + +### Model Architecture: open source [YOLOV7 Model](https://github.com/WongKinYiu/yolov7) + +YOLOV7 is based on the original YOLO paper: [YOLOV1](https://arxiv.org/abs/1506.02640) which was published in 2015 and presented a leap in inference speed for object detection models. The main reason for this was that it was one of the first models that did object detection in a single stage, hence the name YOLO ( you only look once ) in contrast to the two stage models that were popular at the time. Note that some single stage models were present, such as SSD, but they had relatively poor accuracy performance. + +#### Core ideas used in the YOLOV1 paper: + +The original paper was trained on input images of size 448x448 and these images where parsed into a grid of 7x7 grid cells. +
+ Example Image +
Original grid cells on 448x448 image. Source: "You Only Look Once by Joseph Redmon et al., CVPR 2016.
+
+ +The idea was that each grid cell was responsible for predicting a object if the center of that object was within the given grid cell. +**In this example the red grid cell would be responsible to detecting the car present in the top right corner** + + In the paper it was also proposed that each grid cell's output was two bounding boxes each with their own confidence / objectness score + a class probability vector. I.e was each grid cell only capable of predicting one detection, altough it could predict two bounding boxes. The bounding box with the highest objectness score was chosen in addition to the class with the highest probability. + +
+ Example Image +
YOLOv1 output tensor. Source: "You Only Look Once by Joseph Redmon et al., CVPR 2016.
+
+ +In the figure above the core ideas is presented, each grid cell proposing two bounding boxes (in black) but only one class. The "best" box and class over a certain treshold is then used as the final prediction. -> Single stage object detection and classification! + +### Output +Because the model uses a 7x7 grid, the output is a tensor of shape (batch_size, 7, 7, 30). Where 30 is the number of parameters that is predicted for each grid cell. The 30 parameters per grid cell are: +[objectness_score, box_x, box_y, width, height] + [objectness_score, box_x, box_y, width, height] + [class_probabilities]. +Two bounding boxes + a vector of class probabilities. In the case of the original YOLO paper, the model was trained on the [PASCAL dataset](http://host.robots.ox.ac.uk/pascal/VOC/) where there are 20 classes present. Hence the 30 parameters per grid cell. + + +
+ YOLOv1 Output Tensor Visualization +
YOLOv1 output tensor. Source: "You Only Look Once by Joseph Redmon et al., CVPR 2016.
+
+ + +### Training +Training was done on the rangpur cluster using a variety of different GPU's, the two main training runs shown in the plots below was done using the A100 and the P100. (A100 for the batch size of 8 and P100 for the batch size of 32). + +- Hyperparameters that was used is described in: [hyp.scratch.p6.yaml](./hyp.scratch.p6.yaml) + +***In the plots below, the term step refers to epoch number*** + +
+ Example Image +
Box loss.
+
+
+ Example Image +
Class loss.
+
+
+ Example Image +
Object loss.
+
+ + +#### Training discussion +From the plots showing the box, class and object loss it is clear that they indeed was decreasing when the training was halted. With that said they decreased by small amounts, almost negligible. In the case of the A100 training run, that took about 48 hours. + +An interesting observation is the fact that during both runs the model's loss reduced drastically for the first 10 or so epochs, after this the reduction was almost 0. This can be an indication that given the data the models were presented they actually fitted really quickly, but that the data itself might have very complex structures. Hence the model was not able to learn more from the data. + + +### Results +
+ Example Image +
mAP 0.5:0.95.
+
+
+ Example Image +
mAP@0.5.
+
+
+ Example Image +
Precision
+
+
+ Example Image +
Recall
+
+ + +#### Confusion Matrix +Description + + +#### F1 - curve +Description + +#### Precision - Recall curve +Description + +### Results discussion +The results are not very good, and far off what I anticipated when first embarking on this project. The best result was an mAP@0.5 of about 0.718 which is quite poor. + +An important observation is the instability in training related to precision and recall, we can see that for thease metrics the models doesnt improve much past the 10 first epochs, but it has huge variety from epoch to epoch. + +Initially I thought these issues were related to the small batch size, that was the main driver for increasing it to 32 during the second training run. +However this did not improve the results drastically, although it had a significant effect on mAP@0.5. + +I think the reason for the results being as poor as they are is the inherent complexity of the dataset, when I have went through the data personally I often find it really challenging to understand the labels in the ISIC2017 dataset. + +The label unknown / benign is especially tricky to understand as a human because there are a lot of artifacts present in the dataset that is not labeled as benign skin lesions. + +#### Downsampling +The original dataset too large to fit on a student node at rangpur, since the storage limit there is 16gb. Therefore I dowsampled the dataset by a factor of 2 and used this during both training and testing. Originally I thought this wouldn't pose any problem as the original dataset has images of really high resolution, often in the range og 5MB per image. +That being said, the downsamplign is in itself a loss of information as the dimensionality is reduced, so it could be a factor that has contributed to the poor results. + +#### Anchor boxes misaligned +After diving deeper into the intrinsics of the YOLO architecture I have found one major factor which I suspect could enhance the performance by a ton. Using another set of anchor boxes! +The standard anchor boxes utilized by YOLOV7 does not fit that well with the labels present in the ISIC dataset, where we can see that often a bounding box takes up almost the complete image. + +To mitigate this issue I should have performed a custom annchor box analyzis, by doing a clustering on the boxes present in the ISIC dataset and therefore "allow" the model to do bigger predictions. This is something I will try to implement in the future. + +### Example outputs +
+ Example Image +
Batch 0 - Labels
+
+
+ Example Image +
Batch 0 - Predictions
+
+ +
+ Example Image +
Batch 1 - Labels
+
+
+ Example Image +
Batch 1 - Predictions
+
+ + +In these outputs what I discussed related to anchor boxes above is clear, a lot of the labels take up almost all of the image. This is quite far off from the YOLO standard anchor boxes. + +Another way of visualizing this is by the plot below, here we can clearly see an example of how the anchor boxes are misaligned with the labels present in the ISIC dataset. +
+ Example Image +
Anchor comparison
+
diff --git a/lesion_detection_larsmoan/dataset_utils.py b/lesion_detection_larsmoan/dataset_utils.py new file mode 100644 index 000000000..98c58564e --- /dev/null +++ b/lesion_detection_larsmoan/dataset_utils.py @@ -0,0 +1,224 @@ +import os +import cv2 +import pandas as pd +import numpy as np +from PIL import Image +import zipfile +import gdown +import signal +import sys +from tqdm import tqdm +import shutil + + +def visualize_labels(dataset_folder): + for filename in os.listdir(dataset_folder): + if filename.endswith(".txt"): + txt_path = os.path.join(dataset_folder, filename) + image_path = os.path.join(dataset_folder, filename.replace(".txt", ".jpg")) + if os.path.exists(image_path): + draw_yolo_bboxes_on_image(image_path, txt_path) + else: + print("Could not find the corresponding image for", txt_path) + + +#Removes all superpixel images from a folder +def remove_superpixels(folder): + files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))] + for file in files: + if file.endswith('superpixels.png'): + os.remove(os.path.join(folder, file)) + + +#Computes the bounding box of a binary mask, not normalized +def compute_bbx(binary_mask): + # Find the coordinates of non-zero pixels + rows, cols = np.where(binary_mask == 255) + + # Get the minimum and maximum coordinates + x_min, x_max = np.min(cols), np.max(cols) + y_min, y_max = np.min(rows), np.max(rows) + + # Compute the center, width, and height + x_center = (x_min + x_max) / 2 + y_center = (y_min + y_max) / 2 + width = x_max - x_min + height = y_max - y_min + + return x_center, y_center, width, height + +#Based on the binary / segmentaiton images, create bounding boxes, normalize them to YOLO format and fetch the class from the csv file +def create_yolo_labels(seg_folder_path, class_csv_file, dst_folder_path): + labels = pd.read_csv(class_csv_file, index_col=0) + + for filename in tqdm(os.listdir(seg_folder_path), desc="Processing images"): + if filename.endswith(".png"): + image_id = filename[:12] + + # Determine the class based on the dataframe + class_row = labels.loc[image_id] + if class_row['melanoma'] == 1.0: + class_id = 0 + elif class_row['seborrheic_keratosis'] == 1.0: + class_id = 1 + else: + class_id = 2 # Assuming a third class for 'other' or 'nevi' based on the given example + + # Load the binary image + image_path = os.path.join(seg_folder_path, filename) + image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + + # Compute the bounding box + x_center, y_center, width, height = compute_bbx(image) + + # Normalize the coordinates -> YOLO format + x_center /= image.shape[1] + y_center /= image.shape[0] + width /= image.shape[1] + height /= image.shape[0] + + # Write the YOLO bbx with class to a .txt file + output_path = os.path.join(dst_folder_path, filename.replace("_segmentation.png", ".txt")) + with open(output_path, "w") as file: + file.write(f"{class_id} {x_center} {y_center} {width} {height}\n") + + + +def draw_yolo_bboxes_on_image(image_path, txt_path): + image = cv2.imread(image_path) + + # Extracting file ID from image_path + file_id = os.path.splitext(os.path.basename(image_path))[0] + + #color mapping based on class_id + class_colors = { + 0: (0, 255, 0), # Green for Melanoma + 1: (0, 0, 255) # Red for Seborrheic Keratosis + } + + #Resizing factors for displaying + resize_factor_x = 480 / image.shape[1] + resize_factor_y = 480 / image.shape[0] + + resized_image = cv2.resize(image, (480, 480)) + + # Display the file ID on top of the image + cv2.putText(resized_image, file_id, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + + # Read bounding box data from the txt file + with open(txt_path, 'r') as file: + lines = file.readlines() + for line in lines: + class_id, x_center, y_center, width, height = map(float, line.strip().split()) + + # Denormalize the coordinates + x_center *= image.shape[1] + y_center *= image.shape[0] + width *= image.shape[1] + height *= image.shape[0] + + # Adjust the bounding box coordinates based on the resizing factors + x_center *= resize_factor_x + y_center *= resize_factor_y + width *= resize_factor_x + height *= resize_factor_y + + # Convert to top-left x, y coordinates + x_min = int(x_center - width / 2) + y_min = int(y_center - height / 2) + x_max = int(x_center + width / 2) + y_max = int(y_center + height / 2) + + # Define the label and color based on class_id + label = "Melanoma" if class_id == 0 else "Seborrheic Keratosis" if class_id == 1 else "Unknown" + color = class_colors.get(class_id, (255, 255, 255)) # Default color is white + + cv2.rectangle(resized_image, (x_min, y_min), (x_max, y_max), color, 2) + cv2.putText(resized_image, label, (x_min, y_min - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) + + cv2.imshow('Image with Bounding Boxes and Labels', resized_image) + cv2.waitKey(0) + cv2.destroyAllWindows() + +def visualize_labels(dataset_folder): + for filename in os.listdir(dataset_folder): + if filename.endswith(".txt"): + txt_path = os.path.join(dataset_folder, filename) + image_path = os.path.join(dataset_folder, filename.replace(".txt", ".jpg")) + if os.path.exists(image_path): + draw_yolo_bboxes_on_image(image_path, txt_path) + else: + print("Could not find the corresponding image for", txt_path) + + +def downsample_images(input_folder_path, output_folder, downsample_factor): + # Create the output folder if it doesn't exist + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + for filename in tqdm(os.listdir(input_folder_path), desc="Downsampling images"): + if filename.endswith(".jpg"): + filepath = os.path.join(input_folder_path, filename) + + try: + # Load the image + img = cv2.imread(filepath) + + # Check if the image is loaded correctly + if img is None: + print(f"Failed to load {filename}. Skipping...") + continue + + # Calculate the new size + new_size = (img.shape[1] // downsample_factor, img.shape[0] // downsample_factor) + + # Resize the image + resized_img = cv2.resize(img, new_size, interpolation=cv2.INTER_LINEAR) + + # Save the downsampled image to the output folder + cv2.imwrite(os.path.join(output_folder, filename), resized_img) + + except Exception as e: + print(f"Error processing {filename}: {e}") + + + +def copy_txt_files(source_folder, destination_folder): + # Ensure destination directory exists; if not, create it + if not os.path.exists(destination_folder): + os.makedirs(destination_folder) + + for filename in tqdm(os.listdir(source_folder), desc="Copying label files"): + if filename.endswith('.txt'): + source_path = os.path.join(source_folder, filename) + destination_path = os.path.join(destination_folder, filename) + + shutil.copy2(source_path, destination_path) + + +#Used to download and unzip compressed dataset from google drive if the dataset folder doesn't exist +def download_dataset(dataset_folder, download_url="https://drive.google.com/uc?id=1YI3pwanX35i7NCIxKnfXBozXiyQZcGbL"): + if not os.path.exists(dataset_folder): + print(f"Folder {dataset_folder} does not exist. Downloading the zip file...") + + # Download zip file using gdown + output = "temp_download.zip" + gdown.download(download_url, output, quiet=False) + + # Unzip the downloaded file + print("Unzipping the downloaded file...") + with zipfile.ZipFile(output, 'r') as zip_ref: + zip_ref.extractall(dataset_folder) + + # Delete the downloaded zip file + os.remove(output) + print(f"Files have been extracted to {dataset_folder}") + else: + print(f"Folder {dataset_folder} already exists.") + +if __name__ == "__main__": + #Download the preprocessed version of ISIC2017 that has YOLO labels and correct structure + path_to_dset = "/home/Student/s4827064/PatternAnalysis-2023/lesion_detection_larsmoan/data/" + download_dataset(path_to_dset) + + \ No newline at end of file diff --git a/lesion_detection_larsmoan/figures/anchor_comparison.png b/lesion_detection_larsmoan/figures/anchor_comparison.png new file mode 100644 index 000000000..05c6becc6 Binary files /dev/null and b/lesion_detection_larsmoan/figures/anchor_comparison.png differ diff --git a/lesion_detection_larsmoan/figures/grid_cell_yolo.png b/lesion_detection_larsmoan/figures/grid_cell_yolo.png new file mode 100644 index 000000000..318049b29 Binary files /dev/null and b/lesion_detection_larsmoan/figures/grid_cell_yolo.png differ diff --git a/lesion_detection_larsmoan/figures/output-tensor-yolov1.png.webp b/lesion_detection_larsmoan/figures/output-tensor-yolov1.png.webp new file mode 100644 index 000000000..5fdd581e1 Binary files /dev/null and b/lesion_detection_larsmoan/figures/output-tensor-yolov1.png.webp differ diff --git a/lesion_detection_larsmoan/figures/yolov1.png b/lesion_detection_larsmoan/figures/yolov1.png new file mode 100644 index 000000000..e9b5500f1 Binary files /dev/null and b/lesion_detection_larsmoan/figures/yolov1.png differ diff --git a/lesion_detection_larsmoan/hyp.scratch.p5.yaml b/lesion_detection_larsmoan/hyp.scratch.p5.yaml new file mode 100644 index 000000000..d7dac63ca --- /dev/null +++ b/lesion_detection_larsmoan/hyp.scratch.p5.yaml @@ -0,0 +1,31 @@ +lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3) +lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf) +momentum: 0.937 # SGD momentum/Adam beta1 +weight_decay: 0.0005 # optimizer weight decay 5e-4 +warmup_epochs: 3.0 # warmup epochs (fractions ok) +warmup_momentum: 0.8 # warmup initial momentum +warmup_bias_lr: 0.1 # warmup initial bias lr +box: 0.05 # box loss gain +cls: 0.3 # cls loss gain +cls_pw: 1.0 # cls BCELoss positive_weight +obj: 0.7 # obj loss gain (scale with pixels) +obj_pw: 1.0 # obj BCELoss positive_weight +iou_t: 0.20 # IoU training threshold +anchor_t: 4.0 # anchor-multiple threshold +# anchors: 3 # anchors per output layer (0 to ignore) +fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5) +hsv_h: 0.015 # image HSV-Hue augmentation (fraction) +hsv_s: 0.7 # image HSV-Saturation augmentation (fraction) +hsv_v: 0.4 # image HSV-Value augmentation (fraction) +degrees: 0.0 # image rotation (+/- deg) +translate: 0.2 # image translation (+/- fraction) +scale: 0.9 # image scale (+/- gain) +shear: 0.0 # image shear (+/- deg) +perspective: 0.0 # image perspective (+/- fraction), range 0-0.001 +flipud: 0.5 # image flip up-down (probability) +fliplr: 0.5 # image flip left-right (probability) +mosaic: 1.0 # image mosaic (probability) +mixup: 0.15 # image mixup (probability) +copy_paste: 0.0 # image copy paste (probability) +paste_in: 0.15 # image copy paste (probability), use 0 for faster training +loss_ota: 1 # use ComputeLossOTA, use 0 for faster training \ No newline at end of file diff --git a/lesion_detection_larsmoan/hyp.scratch.p6.yaml b/lesion_detection_larsmoan/hyp.scratch.p6.yaml new file mode 100644 index 000000000..7611b21cb --- /dev/null +++ b/lesion_detection_larsmoan/hyp.scratch.p6.yaml @@ -0,0 +1,31 @@ +lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3) +lrf: 0.2 # final OneCycleLR learning rate (lr0 * lrf) +momentum: 0.937 # SGD momentum/Adam beta1 +weight_decay: 0.0005 # optimizer weight decay 5e-4 +warmup_epochs: 3.0 # warmup epochs (fractions ok) +warmup_momentum: 0.8 # warmup initial momentum +warmup_bias_lr: 0.1 # warmup initial bias lr +box: 0.05 # box loss gain +cls: 0.3 # cls loss gain +cls_pw: 1.0 # cls BCELoss positive_weight +obj: 0.7 # obj loss gain (scale with pixels) +obj_pw: 1.0 # obj BCELoss positive_weight +iou_t: 0.20 # IoU training threshold +anchor_t: 4.0 # anchor-multiple threshold +# anchors: 3 # anchors per output layer (0 to ignore) +fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5) +hsv_h: 0.015 # image HSV-Hue augmentation (fraction) +hsv_s: 0.7 # image HSV-Saturation augmentation (fraction) +hsv_v: 0.4 # image HSV-Value augmentation (fraction) +degrees: 0.0 # image rotation (+/- deg) +translate: 0.2 # image translation (+/- fraction) +scale: 0.9 # image scale (+/- gain) +shear: 0.0 # image shear (+/- deg) +perspective: 0.0 # image perspective (+/- fraction), range 0-0.001 +flipud: 0.5 # image flip up-down (probability) +fliplr: 0.5 # image flip left-right (probability) +mosaic: 1.0 # image mosaic (probability) +mixup: 0.15 # image mixup (probability) +copy_paste: 0.0 # image copy paste (probability) +paste_in: 0.15 # image copy paste (probability), use 0 for faster training +loss_ota: 1 # use ComputeLossOTA, use 0 for faster training \ No newline at end of file diff --git a/lesion_detection_larsmoan/isic_mrcnn.py b/lesion_detection_larsmoan/isic_mrcnn.py new file mode 100644 index 000000000..eaaefbcf3 --- /dev/null +++ b/lesion_detection_larsmoan/isic_mrcnn.py @@ -0,0 +1,64 @@ +import os +import sys +import numpy as np +import skimage.draw +import pandas as pd +from mrcnn.mrcnn.config import Config +from mrcnn.mrcnn.utils import * +from mrcnn.mrcnn.model import * #This will fail as mrcnn is retarded + +from utils import get_data_dir + + +# Root directory of the project +class ISICConfig(Config): + NAME = "ISIC" + IMAGES_PER_GPU = 2 + + NUM_CLASSES = 1 + 3 # Background + 3 classes for lesions: (melanoma, seborrheic keratosis and benign / Uknown) + + STEPS_PER_EPOCH = 100 + + DETECTION_MIN_CONFIDENCE = 0.7 + + +class ISICDataset(Dataset): + "Should load a subset of our dataset, such that the same class can be used for loading training and validation dataset" + + def load_isic(self, dataset_dir, subset): + self.add_class("ISIC", 0, "melanoma") + self.add_class("ISIC", 1, 'seborrheic_keratosis') + self.add_class("ISIC", 2, "benign / Unknown") + + assert subset in ["train", "val"] + if subset == "labels": + #Load a df and save it + self.labels = pd.read_csv(get_data_dir() / dataset_dir/ "") + dataset_dir = os.path.join(dataset_dir, subset) + + for filename in os.listdir(dataset_dir): + print(filename) + #Get the size for each picture + image_path = os.join(get_data_dir() / dataset_dir, filename) + height, width = skimage.io.imread(image_path).shape[:2] + + self.add_image("ISIC", + image_id=filename, + image_path=image_path, + width=width, + height=height, + polygons=None) + + + + def load_mask(self, image_id): + #Should return an array of masks, one for each instance present in the image + + #Note: specifically for the ISIC dataset there is always only one instances per image + image_info = self.image_info[image_id] + + +if __name__ == "__main__": + dset = ISICDataset() + dset.load_isic(get_data_dir() / "data" / "ISIC", "train") + diff --git a/lesion_detection_larsmoan/preprocess_dataset.ipynb b/lesion_detection_larsmoan/preprocess_dataset.ipynb new file mode 100644 index 000000000..61abd4acb --- /dev/null +++ b/lesion_detection_larsmoan/preprocess_dataset.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from dataset_utils import *\n", + "from utils import get_data_dir" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Use this function on the corresponding folders with superpixels, as they are not needed for our yolo model\n", + "# train/\n", + "# val/\n", + "# test/ \n", + "remove_superpixels(get_data_dir() / 'ISIC_2017/test')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Compute the yolo labels and place them in the same folder as the images\n", + "create_yolo_labels(\n", + " get_data_dir() / 'ISIC_2017_orig/train_seg', \n", + " get_data_dir() / 'ISIC_2017_orig/train_labels.csv', \n", + " get_data_dir() / 'ISIC_2017_orig/train_data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "visualize_labels(get_data_dir() / 'ISIC_2017/test')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downsampling images: 0%| | 0/301 [00:00