{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":9988,"databundleVersionId":868324,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-29T14:21:44.813608Z","iopub.execute_input":"2026-01-29T14:21:44.813908Z","iopub.status.idle":"2026-01-29T14:21:48.514291Z","shell.execute_reply.started":"2026-01-29T14:21:44.813875Z","shell.execute_reply":"2026-01-29T14:21:48.513488Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# optimized_ship_prep_train.py\n\nimport shutil\nimport os\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom concurrent.futures import ThreadPoolExecutor\nfrom sklearn.model_selection import train_test_split\nfrom ultralytics import YOLO\n\n# -------------- CONFIG --------------\nBASE_DIR = Path(\"/kaggle/input/airbus-ship-detection\")\nTRAIN_IMG_DIR = BASE_DIR / \"train_v2\"\nCSV_PATH = BASE_DIR / \"train_ship_segmentations_v2.csv\"\n\nWORK_DIR = Path(\"/kaggle/working/yolo_dataset\")\nIM_SIZE = 768  # chosen tile size (CO3D chip size example)\nGSD_M_PER_PIXEL = 0.5  # ground sampling distance in meters/pixel (example)\nNUM_EMPTY_SAMPLE = 1000\nN_WORKERS = 8\n\n# create structure\nfor p in [\n    WORK_DIR / \"images\" / \"train\",\n    WORK_DIR / \"images\" / \"val\",\n    WORK_DIR / \"labels\" / \"train\",\n    WORK_DIR / \"labels\" / \"val\",\n]:\n    p.mkdir(parents=True, exist_ok=True)\n\n# -------------- UTILS --------------\ndef rle_to_bbox_direct(rle: str, width: int = 768, height: int = 768):\n    \"\"\"\n    Convert RLE (space separated \"start length start length ...\") to bbox.\n    Returns YOLO-format bbox [class, x_center, y_center, w, h] (normalized).\n    This avoids building the full mask by computing pixel indices directly.\n    \"\"\"\n    if not isinstance(rle, str) or rle.strip() == \"\":\n        return None\n\n    s = list(map(int, rle.split()))\n    starts = np.asarray(s[0::2], dtype=np.int64) - 1  # zero-based\n    lengths = np.asarray(s[1::2], dtype=np.int64)\n    if len(starts) == 0:\n        return None\n\n    # Compute start and end linear indices\n    ends = starts + lengths  # end is exclusive\n\n    # Collect min and max linear indices\n    min_idx = np.min(starts)\n    max_idx = np.max(ends - 1)\n\n    # Convert linear indices to (row, col). RLE used in this dataset lists\n    # linear indices for columns first (i.e., Fortran order), so mapping:\n    # row = index % height, col = index // height\n    # NOTE: this rule depends on dataset; visually verify on sample images.\n    rows = np.array([min_idx % height, max_idx % height])\n    cols = np.array([min_idx // height, max_idx // height])\n\n    y_min, y_max = rows.min(), rows.max()\n    x_min, x_max = cols.min(), cols.max()\n\n    # Inclusive pixel extents -> add +1 to compute width/height in pixels\n    w_px = (x_max - x_min) + 1\n    h_px = (y_max - y_min) + 1\n    x_center = x_min + w_px / 2.0\n    y_center = y_min + h_px / 2.0\n\n    # Normalize\n    x_c = x_center / width\n    y_c = y_center / height\n    w = w_px / width\n    h = h_px / height\n\n    # Class 0 is ship\n    return [0, float(x_c), float(y_c), float(w), float(h)]\n\n# -------------- LOAD CSV & GROUP --------------\nprint(\"Reading CSV...\")\ndf = pd.read_csv(CSV_PATH)\ndf['has_ship'] = df['EncodedPixels'].notna()\n\ndf_ships = df[df['has_ship']]\ndf_empty = df[~df['has_ship']].sample(n=NUM_EMPTY_SAMPLE, random_state=42)\ndf_balanced = pd.concat([df_ships, df_empty], ignore_index=True)\n\n# group RLEs by ImageId -> list of EncodedPixels\ngrouped = df_balanced.groupby('ImageId')['EncodedPixels'].apply(list).to_dict()\nall_images = list(grouped.keys())\n\ntrain_ids, val_ids = train_test_split(all_images, test_size=0.10, random_state=42)\n\n# -------------- PROCESSING FUNCTION (parallel file copy & label write) --------------\ndef process_one_image(img_id: str, split: str = 'train'):\n    src = TRAIN_IMG_DIR / img_id\n    dst_img = WORK_DIR / \"images\" / split / img_id\n    dst_lbl = WORK_DIR / \"labels\" / split / img_id.replace('.jpg', '.txt')\n\n    if not src.exists():\n        return False\n\n    # Copy image\n    shutil.copy2(src, dst_img)\n\n    # Compose bboxes\n    rles = grouped.get(img_id, [])\n    bboxes = []\n    for rle in rles:\n        if isinstance(rle, str) and rle.strip():\n            bb = rle_to_bbox_direct(rle, width=IM_SIZE, height=IM_SIZE)\n            if bb:\n                bboxes.append(bb)\n\n    # Write label file (possibly empty)\n    with open(dst_lbl, 'w') as f:\n        for b in bboxes:\n            f.write(f\"{b[0]} {b[1]:.6f} {b[2]:.6f} {b[3]:.6f} {b[4]:.6f}\\n\")\n    return True\n\ndef process_batch_parallel(image_ids, split='train'):\n    print(f\"Processing {split} ({len(image_ids)} images) with {N_WORKERS} workers...\")\n    with ThreadPoolExecutor(max_workers=N_WORKERS) as ex:\n        list(tqdm(ex.map(lambda id_: process_one_image(id_, split), image_ids), total=len(image_ids)))\n\n# Run conversion\nprocess_batch_parallel(train_ids, 'train')\nprocess_batch_parallel(val_ids, 'val')\n\n# -------------- write data.yaml for Ultralytics --------------\nyaml_content = f\"\"\"path: {WORK_DIR}\ntrain: images/train\nval: images/val\nnc: 1\nnames: ['ship']\n\"\"\"\n\n(WORK_DIR / \"data.yaml\").write_text(yaml_content)\nprint(\"Data prepared and data.yaml written.\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-29T14:21:48.515732Z","iopub.execute_input":"2026-01-29T14:21:48.516094Z","iopub.status.idle":"2026-01-29T14:22:50.456245Z","shell.execute_reply.started":"2026-01-29T14:21:48.516064Z","shell.execute_reply":"2026-01-29T14:22:50.455508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-29T14:22:50.457376Z","iopub.execute_input":"2026-01-29T14:22:50.457618Z","iopub.status.idle":"2026-01-29T14:22:54.265489Z","shell.execute_reply.started":"2026-01-29T14:22:50.457593Z","shell.execute_reply":"2026-01-29T14:22:54.264427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom pathlib import Path\nfrom ultralytics import YOLO\n\n# 1. Define Absolute Paths\n# Use /kaggle/working/ as the root\nBASE_WORK_DIR = Path(\"/kaggle/working/yolo_dataset\")\nYAML_PATH = BASE_WORK_DIR / \"data.yaml\"\n\n# 2. Safety Check before starting\nif not YAML_PATH.exists():\n    print(\"❌ ERROR: data.yaml not found at:\", YAML_PATH)\n    print(\"👉 ACTION: You must run your 'Data Preparation' cell first to create this file.\")\nelse:\n    print(\"✅ data.yaml found! Initializing training...\")\n    \n    # 3. Initialize and Train\n    model = YOLO(\"yolov8n.pt\") \n\n    model.train(\n        data=str(YAML_PATH),  # Use the absolute path string\n        epochs=50,\n        imgsz=768,\n        batch=16,\n        device=0,             # Ensure GPU is enabled in Kaggle Settings\n        workers=4,\n        project=\"/kaggle/working/runs\", # Force output to kaggle/working\n        name=\"ship_detection_v1\"\n    )\n    \n    # Export\n    best_weights = \"/kaggle/working/runs/ship_detection_v1/weights/best.pt\"\n    if os.path.exists(best_weights):\n        model = YOLO(best_weights)\n        model.export(format=\"onnx\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-29T14:22:54.267088Z","iopub.execute_input":"2026-01-29T14:22:54.267400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n!ls -R /kaggle/working/yolo_dataset","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}