From 95e13af71a55f3810fbec72c9ddba8ae035ab493 Mon Sep 17 00:00:00 2001 From: Jeffrey Joan Sam Date: Sat, 28 Sep 2024 12:09:42 -0500 Subject: [PATCH 1/6] code to convert mask to polygon and other util functions --- .DS_Store | Bin 0 -> 6148 bytes preprocess_data/utils.py | 214 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 .DS_Store create mode 100644 preprocess_data/utils.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c05b4f4bc1759c4ee83cdc024357f4ea3253f90e GIT binary patch literal 6148 zcmeHK%}N6?5T3NvZYg39iXH=A3)cDr;$^Az1zgdCO5JsfF0PxhyS0Z>*sH#fZ{qVf zlcY+i;z1BBGiByWCNl~7QZgF=5S?MT1JD2f2bHi?L-U2uIO&{}tcOtOIZ{w?3w_A& z$MPQ-ptD7gCC5tK@h6|BVU6-9A$Z{^(IQ?%F=SxsXBG%+PhaH zFZc6d*74Iz>YXVS2h+A6oJajpud#NllH8AyeyS6qu#X{E7f}+bQAZ7vFw?o78E|S& zt=HHXk9YUmO?h}QY0B|oyVaC?y9bj=%~{{tK056^#E*%3HcSir6KYwpIE5E%{IJO< zFHK^V++pN&mT5L4Gr$Zm0}IE1z380!!tIPV#0)S4zh!{#2Z>7PS}Y9es{;*vKGJxF zkObTGmLQZCU5kZ596=E#715*$`@|3?9sSbAxfTn9CLM%c8RxMp3;RM5dUfZ|H=37|Jfw&F$2uNLNOpJU9a20E!o;Sw>he{67>d^gyIT=A1T<- hr5IzW6mOzxLBFI1qHD1*h#nOF5YRNRVFvz`fp-LUQQiOm literal 0 HcmV?d00001 diff --git a/preprocess_data/utils.py b/preprocess_data/utils.py new file mode 100644 index 0000000..9b9441a --- /dev/null +++ b/preprocess_data/utils.py @@ -0,0 +1,214 @@ +import cv2 +import os +import numpy as np +from shapely.geometry import Polygon +from PIL import Image, ImageDraw +import shutil +from sklearn.model_selection import train_test_split +import pandas as pd +from pathlib import Path + + +colors = [ + (255, 0, 0), # Red + (0, 255, 0), # Green + (0, 0, 255), # Blue + (255, 255, 0), # Yellow + (255, 0, 255), # Magenta + (0, 255, 255), # Cyan + (128, 128, 128) # Gray + ] + +def mask_to_polygons_multi(mask): + h, w = mask.shape + polygons = [] + labels = [] + annotations = [] + + for label in np.unique(mask): + if label == 0: + continue # Skip background + + binary_mask = (mask == label).astype(np.uint8) + contours, _ = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + for contour in contours: + epsilon = 0.005 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + + # Check if the contour has at least 4 points + if len(approx) < 4: + continue # Skip this contour if it has fewer than 4 points + + try: + poly = Polygon(approx.reshape(-1, 2)) + poly = poly.simplify(1, preserve_topology=True) + + if poly.is_valid and poly.area > 0: + min_x, min_y, max_x, max_y = poly.bounds + + coords = np.array(poly.exterior.coords) + coords[:, 0] /= w + coords[:, 1] /= h + + polygons.append(coords.flatten().tolist()) + labels.append(int(label)) + + bbox = [min_x, min_y, max_x - min_x, max_y - min_y] + + annotations.append({ + "segmentation": [coords.flatten().tolist()], + "category_id": int(label), + "bbox": bbox, + "area": poly.area + }) + except Exception as e: + print(f"Error processing contour: {e}") + continue # Skip this contour if there's any error + + return polygons, labels, annotations + +def annotations_to_mask(annotations, image_shape=(256,256)): + """Convert COCO-style annotations back to a mask.""" + mask = np.zeros(image_shape, dtype=np.uint8) + for ann in annotations: + polygon = np.array(ann['segmentation'][0]).reshape(-1, 2) + polygon = (polygon * np.array([image_shape[1], image_shape[0]])).astype(int) + cv2.fillPoly(mask, [polygon], int(ann['category_id'] + 1)) + return mask + + +def overlay_mask_on_image_with_boxes(image, mask, annotations, alpha=0.5): + """Overlay a colored mask on an image with bounding boxes and labels.""" + # Convert grayscale image to RGB + if len(image.shape) == 2 or image.shape[2] == 1: + image_rgb = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + else: + image_rgb = image + + # Define a color map with distinct colors for each class + + + # Create a colored mask + colored_mask = np.zeros((*mask.shape, 3), dtype=np.uint8) + for i in range(1, mask.max() + 1): + colored_mask[mask == i] = colors[i % len(colors)] + + # Overlay the mask on the image + overlay = (image_rgb * (1 - alpha) + colored_mask * alpha).astype(np.uint8) + overlay_image = Image.fromarray(overlay) + draw = ImageDraw.Draw(overlay_image) + + # Draw bounding boxes + for ann in annotations: + bbox = ann['bbox'] + x, y, w, h = bbox + color = colors[(ann['category_id']) % len(colors)] + draw.rectangle([x, y, x+w, y+h], outline=color, width=2) + + return np.array(overlay_image) + +def extract_instance(image, mask, instance_id): + """Extract a single instance from the image using the mask.""" + instance_mask = (mask == instance_id).astype(np.uint8) * instance_id + roi = cv2.bitwise_and(image, image, mask=(instance_mask > 0).astype(np.uint8)) + return roi, instance_mask + +def paste_instance(base_image, roi, instance_mask): + """Paste the extracted ROI onto a copy of the base image.""" + result_image = base_image.copy() + result_image[instance_mask > 0] = roi[instance_mask > 0] + return result_image + +def process_mask_images_to_poygon_txt(input_dir, output_dir): + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + mask_files = [f for f in os.listdir(input_dir) if f.endswith(('.png', '.jpg', '.tif'))] + + for mask_file in mask_files: + mask_path = os.path.join(input_dir, mask_file) + mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) + + polygons, labels, _ = mask_to_polygons_multi(mask) + + txt_filename = os.path.splitext(mask_file)[0] + '.txt' + txt_path = os.path.join(output_dir, txt_filename) + + with open(txt_path, 'w') as f: + for polygon, label in zip(polygons, labels): + # Subtract 1 from label to start class indexing from 0 for YOLO format + line = f"{label-1} " + " ".join(map(str, polygon)) + f.write(line + '\n') + +def process_tif_to_png_and_verify(tif_path): + # Load the TIFF image using cv2 + original_image = cv2.imread(tif_path) + + if original_image is None: + raise ValueError(f"Failed to load image: {tif_path}") + + # Construct the output PNG path + png_path = tif_path.replace('.tif', '.png') + + # Save the image as PNG + cv2.imwrite(png_path, original_image) + + # Read back the PNG image + loaded_image = cv2.imread(png_path) + + # Compare the arrays + are_equal = np.array_equal(original_image, loaded_image) + + # Delete the PNG file + os.remove(png_path) + + return are_equal, original_image, loaded_image + + +def create_dataset_df(base_dir): + data = [] + label_dir = os.path.join(base_dir, 'labels') + image_dir = os.path.join(base_dir, 'preprocessed_images') + + # Iterate through all txt files in the label directory + for txt_file in Path(label_dir).glob('*.txt'): + txt_path = str(txt_file.relative_to(base_dir)) + png_name = txt_file.stem + '.png' + png_path = str(Path('preprocessed_images') / png_name) + + # Check if the corresponding image file exists + if not os.path.exists(os.path.join(base_dir, png_path)): + print(f"Warning: Image file not found for {txt_path}") + continue + + # Read labels from the txt file + with open(txt_file, 'r') as f: + for line in f: + label = line.strip().split()[0] # Get the first value of each line + data.append([txt_path, png_path, label]) + + # Create DataFrame + df = pd.DataFrame(data, columns=['label_file', 'image_file', 'label']) + return df + +def split_dataset(df): + # Create unique image-label pairs + unique_pairs = df[['label_file', 'image_file', 'label']].drop_duplicates().reset_index(drop=True) + + # Split into train and test (85:15) + train_val, test = train_test_split(unique_pairs, test_size=0.05, stratify=unique_pairs['label'], random_state=42) + + # Further split train into train and validation (80:20 of the original train set) + train, val = train_test_split(train_val, test_size=0.15, stratify=train_val['label'], random_state=42) + + return train, val, test + +def move_files(df, source_dir, dest_dir): + os.makedirs(dest_dir, exist_ok=True) + for _, row in df.iterrows(): + image_file = os.path.join(source_dir, row['image_file']) + label_file = os.path.join(source_dir, row['label_file']) + + shutil.copy(image_file, dest_dir) + shutil.copy(label_file, dest_dir) \ No newline at end of file From 95bdf24be7e31724e2e194cb640caf8ed9f368f4 Mon Sep 17 00:00:00 2001 From: Jeffrey Joan Date: Fri, 18 Oct 2024 15:38:09 -0500 Subject: [PATCH 2/6] file to create yaml for YOLO training & takes several arguments --- models/create_yaml.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 models/create_yaml.py diff --git a/models/create_yaml.py b/models/create_yaml.py new file mode 100644 index 0000000..029b8b6 --- /dev/null +++ b/models/create_yaml.py @@ -0,0 +1,51 @@ +import argparse +import yaml +import os + +def main(args): + # Load the YAML content + yaml_path = args.yaml_path + dataset_path = args.dataset_path + augment = args.augment + + yaml_content = { + 'path': dataset_path, + 'train': 'images/train', + 'val': 'images/val', + 'test': 'images/test', + 'nc': 1, + 'names': { + 0: 'seep', + }, + 'patience': 20 + } + + + if augment: + yaml_content.update({ + 'hsv_h': 0.015, # Hue augmentation + 'hsv_s': 0.7, # Saturation augmentation + 'hsv_v': 0.4, # Value (brightness) augmentation + 'degrees': 90, # Image rotation (+/- deg) + 'translate': 0.1, # Image translation (+/- fraction) + 'scale': 0.5, # 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.5, # Image mixup (probability) + 'copy_paste': 0.5, # Segment copy-paste (probability) + }) + + # Save the YAML content + with open(yaml_path, 'w') as file: + yaml.dump(yaml_content, file, default_flow_style=False) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Generate YAML file for YOLO training') + parser.add_argument("--yaml-path", required=True, help="Path to save the YAML file") + parser.add_argument("--dataset-path", required=True, help="Path to the dataset directory") + parser.add_argument("--augment", action='store_true', help="Enable data augmentation") + args = parser.parse_args() + main(args) \ No newline at end of file From ed9ec02a43170cd0e81fe77a6667d6589a91e940 Mon Sep 17 00:00:00 2001 From: Jeffrey Joan Date: Sat, 19 Oct 2024 11:50:13 -0500 Subject: [PATCH 3/6] time profiler for layer by layer operations, takes in arguments for shape, yolo path and cuda --- models/utils/time_profiler.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 models/utils/time_profiler.py diff --git a/models/utils/time_profiler.py b/models/utils/time_profiler.py new file mode 100644 index 0000000..9608036 --- /dev/null +++ b/models/utils/time_profiler.py @@ -0,0 +1,33 @@ +import argparse +import torch +from ultralytics import YOLO + +def main(args): + # Load the YOLO model + model = YOLO(args.yolo_path) + + # Create a random input tensor with the specified shape + x = torch.randn((args.batch_size, 3, args.height, args.width), requires_grad=True) + + # Move the model and input to CUDA if specified + if args.cuda: + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + x = x.to(device) + + # Profile the model execution + with torch.autograd.profiler.profile(use_cuda=args.cuda) as prof: + model(x) + + # Print the profiling results + print(prof) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Profile YOLO model execution') + parser.add_argument("--yolo-path", required=True, help="Path to the YOLO model file") + parser.add_argument("--batch-size", type=int, default=1, help="Batch size of the input tensor") + parser.add_argument("--height", type=int, default=1024, help="Height of the input tensor") + parser.add_argument("--width", type=int, default=1280, help="Width of the input tensor") + parser.add_argument("--cuda", action='store_true', help="Use CUDA for profiling") + args = parser.parse_args() + main(args) \ No newline at end of file From 9e0bc2daf5565dde313bd9564fd8566dff33ccd3 Mon Sep 17 00:00:00 2001 From: Jeffrey-Joan <57098615+Jeffrey-Joan@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:13:02 -0500 Subject: [PATCH 4/6] Create readme.md --- models/readme.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 models/readme.md diff --git a/models/readme.md b/models/readme.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/models/readme.md @@ -0,0 +1 @@ + From 35341199f4e6866b6e53df61af5d617078888517 Mon Sep 17 00:00:00 2001 From: Jeffrey-Joan <57098615+Jeffrey-Joan@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:17:28 -0500 Subject: [PATCH 5/6] Update readme.md --- models/readme.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/models/readme.md b/models/readme.md index 8b13789..7f12aab 100644 --- a/models/readme.md +++ b/models/readme.md @@ -1 +1,25 @@ +# Overview +===================================== +The purpose of the 'create_yaml.py' file here is to create a YOLOv8 training YAML file in a specified file path. + +## Usage +------------- + +### Arguments + +The `create_yaml.py` script takes three mandatory arguments: + +1. **Path to Save the YAML File**: + - The directory path where the generated YAML file will be saved. +2. **Path to the Dataset Directory**: + - The directory path containing the dataset used for training. +3. **Data Augmentation Flag**: + - A boolean flag indicating whether data augmentation should be enabled (`True`) or disabled (`False`). + +### Example Command + +To run the script, use the following command format: + +```bash +python create_yaml.py /path/to/save/yaml /path/to/dataset True From 51d4805647722d0f7faabd998325fc3a5872053d Mon Sep 17 00:00:00 2001 From: Jeffrey-Joan <57098615+Jeffrey-Joan@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:30:09 -0500 Subject: [PATCH 6/6] Create readme.md --- models/utils/readme.md | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 models/utils/readme.md diff --git a/models/utils/readme.md b/models/utils/readme.md new file mode 100644 index 0000000..afe51ae --- /dev/null +++ b/models/utils/readme.md @@ -0,0 +1,47 @@ +# Overview +===================================== +This script is designed to profile the execution of a YOLO (You Only Look Once) model using PyTorch and the Ultralytics YOLO library. It allows users to measure the performance of the model on various input sizes and configurations, including the option to use CUDA for accelerated profiling. + +## Usage +------------- + +To use this script, you need to have the following dependencies installed: + +- **Python**: Ensure you have Python installed on your system. +- **PyTorch**: Install PyTorch using `pip install torch`. +- **Ultralytics YOLO**: Install the Ultralytics YOLO library using `pip install ultralytics`. + +### Command-Line Arguments + +The script takes the following command-line arguments: + +#### `--yolo-path` +- **Description**: The path to the YOLO model file. +- **Required**: Yes + +#### `--batch-size` +- **Description**: The batch size of the input tensor. +- **Default**: 1 +- **Type**: Integer + +#### `--height` +- **Description**: The height of the input tensor. +- **Default**: 1024 +- **Type**: Integer + +#### `--width` +- **Description**: The width of the input tensor. +- **Default**: 1280 +- **Type**: Integer + +#### `--cuda` +- **Description**: Use CUDA for profiling if available. +- **Default**: False (CPU) +- **Type**: Boolean (action='store_true') + +### Example Command + +To run the script, use the following command format: + +```bash +python profile_yolo.py --yolo-path /path/to/yolo/model --batch-size 4 --height 640 --width 640 --cuda