{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":123966,"databundleVersionId":14902028,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14426725,"sourceType":"datasetVersion","datasetId":9214753},{"sourceId":14440842,"sourceType":"datasetVersion","datasetId":9223987},{"sourceId":14520698,"sourceType":"datasetVersion","datasetId":9274149},{"sourceId":14520726,"sourceType":"datasetVersion","datasetId":9274169},{"sourceId":721208,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":548604,"modelId":561329}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:54:24.530681Z","iopub.execute_input":"2026-01-12T18:54:24.531199Z","iopub.status.idle":"2026-01-12T18:54:32.174429Z","shell.execute_reply.started":"2026-01-12T18:54:24.531170Z","shell.execute_reply":"2026-01-12T18:54:32.173369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\ndef get_device():\n    if torch.cuda.is_available():\n        return \"cuda\"\n    if hasattr(torch.backends, \"mps\") and torch.backends.mps.is_available():\n        return \"mps\"\n    return \"cpu\"\n\nDEVICE = get_device()\nprint(\"Using device:\", DEVICE)\n\nprint(f\"torch.cuda.is_available(): {torch.cuda.is_available()}\")\nprint(f\"torch.cuda.device_count(): {torch.cuda.device_count()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:54:32.179978Z","iopub.execute_input":"2026-01-12T18:54:32.180342Z","iopub.status.idle":"2026-01-12T18:54:37.997175Z","shell.execute_reply.started":"2026-01-12T18:54:32.180297Z","shell.execute_reply":"2026-01-12T18:54:37.996385Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Introduction\n\nThis notebook started off as an excersize in implementing a Yolov8 network with solid engineerign principles. After understanding the goals of the competition and the goals of Duality in regards to their Falcon synthetic image generating software the focus of this notebook has changed. The intent of the following will be to evaluate how Yolov8 performs with training input being 100% synthetic and to obtain knowledge on real world data handling.","metadata":{}},{"cell_type":"markdown","source":"# Problem \n\nThe problem is to train a CNN on synthetic data and have it predict bounding boxes with the input being a certain class of object on real world geospatial aerial images.","metadata":{}},{"cell_type":"markdown","source":"# Solution\n\nObtain & train on as much data from Duality's Falcon software as time and computational cost permit.","metadata":{}},{"cell_type":"markdown","source":"# Method\n\n- Use scripts provided by Duality to use pretrained Yolov8 model to train on datasets and make predictions provided the input of test images.\n\n- Use scripts provided by Duality to convert results to submissable predictions.","metadata":{}},{"cell_type":"markdown","source":"# Thoughts\n\n- Ease of usee of Falcon software\n- Benefits of generated images for ml tasks\n      - more and more custom data\n      - data integrity\n- Restrictions of synthetic data\n      - generalizition to real world tests? why?\n          - synthetic images lack certain real world occurence too nuanced for the network\n- Will more and more synthetic data trained on a principally sound Yolov8 CNN raise it's ability to predict in this case bounding boxes of structures given the input of real test images?\n","metadata":{}},{"cell_type":"markdown","source":"# Data Check\n\nData check block is inspired by [Yizhuo](https://www.kaggle.com/wangyiz)'s notebook:  [A Practical Pipeline for Geospatial Detection](https://www.kaggle.com/code/wangyiz/a-practical-pipeline-for-geospatial-detection/notebook).","metadata":{}},{"cell_type":"code","source":"from pathlib import Path\nfrom collections import Counter\nfrom PIL import Image\n\nroot_ds1 = \"/kaggle/input/falcon-ds1/Output/2026-01-06-23-37-24\"\nroot_ds2 = \"/kaggle/input/falcon-ds2/Output/2026-01-07-05-46-19\"\nroot_ds3 = \"/kaggle/input/falcon-ds3/Output/2026-01-07-18-08-26\"\n\ndef check_yolo_dataset(root):\n    root = Path(root)\n\n    img_dirs = [root/\"train/images\", root/\"val/images\"]\n    lbl_dirs = [root/\"train/labels\", root/\"val/labels\"]\n\n    imgs, lbls = [], []\n    for d in img_dirs:\n        if d.exists():\n            imgs += list(d.glob(\"*.png\")) + list(d.glob(\"*.jpg\"))\n    for d in lbl_dirs:\n        if d.exists():\n            lbls += list(d.glob(\"*.txt\"))\n\n    print(\"Dataset check for \", root)\n    print(\"Images\", len(imgs))\n    print(\"Labels\", len(lbls))\n\ncheck_yolo_dataset(root_ds1)\ncheck_yolo_dataset(root_ds2)\ncheck_yolo_dataset(root_ds3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:54:42.941311Z","iopub.execute_input":"2026-01-12T18:54:42.941765Z","iopub.status.idle":"2026-01-12T18:54:43.238717Z","shell.execute_reply.started":"2026-01-12T18:54:42.941736Z","shell.execute_reply":"2026-01-12T18:54:43.237848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os \nimport shutil\n\nsource_dir_name = 'duality-ai-lunate-ai-geospatial-object-detection' \n\n# Construct the full source and destination paths\nsrc_path = os.path.join('/kaggle/input/duality-ai-lunate-ai-geospatial-object-detection', source_dir_name)\ndst_path = os.path.join('/kaggle/working', source_dir_name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:54:46.416750Z","iopub.execute_input":"2026-01-12T18:54:46.417727Z","iopub.status.idle":"2026-01-12T18:54:46.422315Z","shell.execute_reply.started":"2026-01-12T18:54:46.417682Z","shell.execute_reply":"2026-01-12T18:54:46.421375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check if the destination directory already exists to avoid errors if the cell is run multiple times\nif not os.path.exists(dst_path):\n    shutil.copytree(src_path, dst_path)\n    print(f\"Copied '{source_dir_name}' to '/kaggle/working/'\")\nelse:\n    print(f\"'{source_dir_name}' already exists in '/kaggle/working/'\")\n\n# You can verify the contents of the new directory\nprint(\"Files in destination:\", os.listdir(dst_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:54:50.237063Z","iopub.execute_input":"2026-01-12T18:54:50.237487Z","iopub.status.idle":"2026-01-12T18:55:10.417778Z","shell.execute_reply.started":"2026-01-12T18:54:50.237445Z","shell.execute_reply":"2026-01-12T18:55:10.416890Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nds1_train = Path(\"/kaggle/input/falcon-ds1/Output/2026-01-06-23-37-24/train/images\")\nds1_val = Path(\"/kaggle/input/falcon-ds1/Output/2026-01-06-23-37-24/val/images\")\n\nds2_train = Path(\"/kaggle/input/falcon-ds2/Output/2026-01-07-05-46-19/train/images\")\nds2_val = Path(\"/kaggle/input/falcon-ds2/Output/2026-01-07-05-46-19/val/images\")\n\nds3_train = Path(\"/kaggle/input/falcon-ds3/Output/2026-01-07-18-08-26/train/images\")\nds3_val = Path(\"/kaggle/input/falcon-ds3/Output/2026-01-07-18-08-26/val/images\")\n\nds4_train = Path(\"/kaggle/input/falcon-ds4/Output/2026-01-09-04-12-18/train/images\")\nds4_val = Path(\"/kaggle/input/falcon-ds4/Output/2026-01-09-04-12-18/val/images\")\n\nds5_train = Path(\"/kaggle/input/falcon-ds5/Output/2026-01-09-04-48-22/train/images\")\nds5_val = Path(\"/kaggle/input/falcon-ds5/Output/2026-01-09-04-48-22/val/images\")\n\nds6_train = Path(\"/kaggle/input/falcon-ds6/Output/2026-01-09-16-58-15/train/images\")\nds6_val = Path(\"/kaggle/input/falcon-ds6/Output/2026-01-09-16-58-15/val/images\")\n\nds7_train = Path(\"/kaggle/input/falcon-ds7/Output/2026-01-10-04-49-08/train/images\")\nds7_val = Path(\"/kaggle/input/falcon-ds7/Output/2026-01-10-04-49-08/val/images\")\n\nds8_train = Path(\"/kaggle/input/falcon-ds8/Output/2026-01-10-18-37-48/train/images\")\nds8_val = Path(\"/kaggle/input/falcon-ds8/Output/2026-01-10-18-37-48/val/images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:10.419642Z","iopub.execute_input":"2026-01-12T18:55:10.420070Z","iopub.status.idle":"2026-01-12T18:55:10.427717Z","shell.execute_reply.started":"2026-01-12T18:55:10.420031Z","shell.execute_reply":"2026-01-12T18:55:10.425985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile \"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/yolo_params.yaml\"\n# %load /kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/yolo_params.yaml\ntrain: [\"/kaggle/input/falcon-ds3/Output/2026-01-07-18-08-26/train/images\"] #Add your own path(s), ex: [train/images, train2/images]\nval:  [\"/kaggle/input/falcon-ds3/Output/2026-01-07-18-08-26/val/images\"] #Add your own path(s), ex:[val/images, val/images]\ntest: \"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/testImages\"\nnc: 2\nnames: ['building', 'vehicle']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:10.428954Z","iopub.execute_input":"2026-01-12T18:55:10.429372Z","iopub.status.idle":"2026-01-12T18:55:10.614669Z","shell.execute_reply.started":"2026-01-12T18:55:10.429324Z","shell.execute_reply":"2026-01-12T18:55:10.613492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Define the current and new file names/paths\nold_file_name = \"train.py\"\nnew_file_name = \"train_script.py\"\n\n# Construct the full paths within the working directory\n# Note: '/kaggle/working/' is the standard writable directory\nsrc = os.path.join(\"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/\", old_file_name)\ndst = os.path.join(\"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/\", new_file_name)\n\n# Rename the file\nos.rename(src, dst)\n\nprint(f\"File '{old_file_name}' renamed to '{new_file_name}'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:14.813219Z","iopub.execute_input":"2026-01-12T18:55:14.813568Z","iopub.status.idle":"2026-01-12T18:55:14.820682Z","shell.execute_reply.started":"2026-01-12T18:55:14.813544Z","shell.execute_reply":"2026-01-12T18:55:14.819679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile \"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/train_script.py\"\nimport argparse\nfrom ultralytics import YOLO\nimport os\nimport sys\nimport torch\n\n# Define a function to encapsulate the training logic\ndef run_training(epochs, mosaic, optimizer, momentum, lr0, lrf, single_cls):\n    # In a notebook, __file__ might not be defined. Use the current working directory.\n    this_dir = os.getcwd() \n    \n    print(f\"Starting training for {epochs} epochs...\")\n\n    # Ensure model and data files exist in the current directory or specify their paths\n    # For a Kaggle notebook, you'd need to upload yolov8s.pt and yolo_params.yaml\n    model_path = os.path.join(\"/kaggle/working\", \"yolov8s.pt\")\n    data_path = os.path.join(\"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts\", \"yolo_params.yaml\")\n\n    # The YOLO model will download the weights if yolov8s.pt is not found.\n    # We will instantiate it directly.\n    model = YOLO(\"yolov8n.pt\") \n\n    results = model.train(\n        data=data_path,\n        epochs=epochs,\n        device=\"cpu\", # Assuming a GPU is available in Kaggle environment\n        single_cls=single_cls,\n        mosaic=mosaic,\n        optimizer=optimizer,\n        lr0=lr0,\n        lrf=lrf,\n        momentum=momentum\n    )\n\n    return results, model\n\nif __name__ == '__main__':\n    # This block is for when you download and run it as a standard Python script\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--epochs', type=int, default=10, help='Number of epochs')\n    # ... (add all other argparse arguments here for completeness if needed) ...\n    args = parser.parse_args()\n    \n    # You would need to pass default values or user inputs here if running from CLI\n    # run_training(args.epochs, ...) \n    pass # In a notebook context, we call the function directly below.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:17.353924Z","iopub.execute_input":"2026-01-12T18:55:17.354272Z","iopub.status.idle":"2026-01-12T18:55:17.361445Z","shell.execute_reply.started":"2026-01-12T18:55:17.354224Z","shell.execute_reply":"2026-01-12T18:55:17.360502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\n\n# Add the working directory path to the system path\nsys.path.append('/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts')\nimport train_script\nimport predict\nimport convert_preds_to_csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:19.558984Z","iopub.execute_input":"2026-01-12T18:55:19.559890Z","iopub.status.idle":"2026-01-12T18:55:25.359716Z","shell.execute_reply.started":"2026-01-12T18:55:19.559847Z","shell.execute_reply":"2026-01-12T18:55:25.358853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define your parameters as variables within the notebook\nEPOCHS = 50\nMOSAIC = 0.4\nOPTIMIZER = 'AdamW'\nMOMENTUM = 0.9\nLR0 = 0.0001\nLRF = 0.0001\nSINGLE_CLS = False\n\n# Call the function directly with your parameters\n# Make sure your yolov8s.pt model file and yolo_params.yaml data file are in your notebook's working directory\ntraining_results = train_script.run_training(\n    epochs=EPOCHS,\n    mosaic=MOSAIC,\n    optimizer=OPTIMIZER,\n    momentum=MOMENTUM,\n    lr0=LR0,\n    lrf=LRF,\n    single_cls=SINGLE_CLS\n)\n\nprint(\"Training completed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T18:55:25.361216Z","iopub.execute_input":"2026-01-12T18:55:25.361742Z","iopub.status.idle":"2026-01-12T18:55:31.478887Z","shell.execute_reply.started":"2026-01-12T18:55:25.361711Z","shell.execute_reply":"2026-01-12T18:55:31.477118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import joblib\nimport pandas as pd\nfrom pathlib import Path\n\n\nresults, model = training_results\n\n\n\n# Save the model\nsave_path = Path('/kaggle/working/yolov8s.pt')\nmodel.save(save_path)\nprint(f\"Model saved to {save_path}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-11T18:36:13.223158Z","iopub.status.idle":"2026-01-11T18:36:13.223604Z","shell.execute_reply.started":"2026-01-11T18:36:13.223438Z","shell.execute_reply":"2026-01-11T18:36:13.223462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import yaml\nfrom ultralytics import YOLO\n\n\ndef run_prediction_pipeline(base_dir, yolo_params_file_name='yolo_params.yaml'):\n    \"\"\"\n    Main pipeline function to load model, run predictions, and save results.\n\n    Args:\n        base_dir (Path or str): The base directory containing yolo_params.yaml, \n                                 'runs' folder, and the test images directory.\n        yolo_params_file_name (str): The name of the parameters file.\n    \"\"\"\n    base_dir = Path(base_dir)\n    \n    # Load parameters\n    with open(\"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/scripts/yolo_params.yaml\", 'r') as file:\n        data = yaml.safe_load(file)\n\n    if 'test' not in data or data['test'] is None:\n        print(\"No 'test' field found in yolo_params.yaml, please add the test field with the path to the test images\")\n        return\n\n    images_dir = base_dir / data['test'] / 'images'\n\n    # Check that the images directory exists and is not empty\n    if not images_dir.exists() or not images_dir.is_dir() or not any(images_dir.iterdir()):\n        print(f\"Images directory {images_dir} is invalid or empty\")\n        return\n\n    # Load the YOLO model (assuming 'runs/detect' structure relative to base_dir)\n    detect_path = base_dir / \"runs\" / \"detect\"\n    train_folders = [f for f in os.listdir(detect_path) if os.path.isdir(detect_path / f) and f.startswith(\"train\")]\n    \n    if len(train_folders) == 0:\n        raise ValueError(\"No training folders found\")\n\n    # Use the latest (or only) training run folder for simplicity in automated environment\n    # You might want a more robust way to select if multiple exist\n    selected_train_folder = train_folders[-1] \n    model_path = detect_path / selected_train_folder / \"weights\" / \"best.pt\"\n    \n    # Check if model exists\n    if not model_path.exists():\n        raise FileNotFoundError(f\"Model file not found at {model_path}\")\n\n    model = YOLO(model_path)\n\n    # Output directory\n    output_dir = base_dir / \"predictions\"\n    images_output_dir = output_dir / 'images'\n    labels_output_dir = output_dir / 'labels'\n    images_output_dir.mkdir(parents=True, exist_ok=True)\n    labels_output_dir.mkdir(parents=True, exist_ok=True)\n\n    # Iterate through the images\n    for img_path in images_dir.glob('*'):\n        if img_path.suffix.lower() not in ['.png', '.jpg', '.jpeg']:\n            continue\n        \n        output_path_img = images_output_dir / img_path.name\n        output_path_txt = labels_output_dir / img_path.with_suffix('.txt').name\n        predict.predict_and_save(model, img_path, output_path_img, output_path_txt)\n\n    print(f\"Predicted images saved in {images_output_dir}\")\n    print(f\"Bounding box labels saved in {labels_output_dir}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-11T18:49:22.220894Z","iopub.execute_input":"2026-01-11T18:49:22.221533Z","iopub.status.idle":"2026-01-11T18:49:22.238379Z","shell.execute_reply.started":"2026-01-11T18:49:22.221489Z","shell.execute_reply":"2026-01-11T18:49:22.236825Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"run_prediction_pipeline(base_dir=\"/kaggle/working\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-11T18:41:15.299032Z","iopub.execute_input":"2026-01-11T18:41:15.299445Z","iopub.status.idle":"2026-01-11T18:49:22.216372Z","shell.execute_reply.started":"2026-01-11T18:41:15.299410Z","shell.execute_reply":"2026-01-11T18:49:22.214429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_images_folder = \"/kaggle/working/duality-ai-lunate-ai-geospatial-object-detection/testImages/images\"\nconvert_preds_to_csv.predictions_to_csv(test_images_folder=test_images_folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-11T18:51:09.576611Z","iopub.execute_input":"2026-01-11T18:51:09.577066Z","iopub.status.idle":"2026-01-11T18:51:10.104555Z","shell.execute_reply.started":"2026-01-11T18:51:09.577028Z","shell.execute_reply":"2026-01-11T18:51:10.103460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}