{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "사용 및 코드 설명\n",
    "Cell 1 (초기 설정):\n",
    "전역 변수(경로, JSON 파일 경로, 이미지 복사/라벨링 폴더 등)를 한 곳에 모아두어 이후 셀에서 일괄 참조할 수 있도록 했습니다.\n",
    "\n",
    "Cell 2 (COCO JSON 생성):\n",
    "어노테이션 파일들을 읽어 이미지 정보와 annotation, 카테고리 정보를 COCO 포맷으로 변환합니다.\n",
    "이미지 경로가 올바르지 않을 경우 (예, /root/...) 자동으로 /DATA2/ltb를 prefix로 붙여 보정합니다.\n",
    "디버깅 메시지를 통해 처음 몇 개의 경로를 확인할 수 있습니다.\n",
    "\n",
    "Cell 3 (카테고리 목록 구성):\n",
    "동적 카테고리 매핑을 기반으로 COCO 카테고리 리스트를 생성합니다.\n",
    "\n",
    "Cell 4 (최종 COCO JSON 저장):\n",
    "수집한 이미지, annotation, 카테고리 정보를 하나의 JSON 파일로 저장합니다.\n",
    "\n",
    "Cell 5 (Train/Val 분할):\n",
    "합쳐진 COCO JSON 파일(OUTPUT_JSON_PATH)을 train/val로 분할하는 함수를 정의하고 실행합니다.\n",
    "val_ratio와 seed 값을 통해 분할 비율과 재현성을 보장합니다.\n",
    "\n",
    "Cell 6 (이미지 복사):\n",
    "train.json과 val.json에 기록된 이미지 파일들을 원본(IMAGES_SRC_DIR)에서 지정한 목적지 폴더로 병렬 복사합니다.\n",
    "복사 전, 경로 보정 및 실제 파일 존재 여부를 확인하며 디버깅 메시지를 출력합니다.\n",
    "\n",
    "Cell 7 (라벨링 이미지 생성):\n",
    "지정된 JSON(예, val.json)에 기록된 이미지에 대해 바운딩 박스와 클래스 레이블을 그려서 새로운 폴더에 저장합니다.\n",
    "각 클래스에 대해 자동으로 색상을 할당하며, 진행 상황은 tqdm로 표시합니다.\n",
    "\n",
    "이와 같이 구성하면,\n",
    "\n",
    "어노테이션 합치기 → 2) JSON 분할 → 3) 이미지 복사 → 4) 라벨링 이미지 생성\n",
    "까지 연속적으로 실행할 수 있으며, 각 셀은 독립적으로 재사용 가능하므로 필요한 기능만 별도로 실행할 수도 있습니다.\n",
    "\n",
    "각 셀의 변수(경로, 비율 등)는 Cell 1의 전역 변수에서 쉽게 수정할 수 있으므로, 다른 데이터셋에도 유연하게 적용 가능합니다.\n",
    "\n",
    "이제 전체 코드를 별도의 ipynb 파일로 저장하여 사용하시면 됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 1: 초기 설정 및 라이브러리 임포트]\n",
    "import os\n",
    "import json\n",
    "import glob\n",
    "import shutil\n",
    "from pathlib import Path\n",
    "from concurrent.futures import ThreadPoolExecutor, as_completed\n",
    "from collections import defaultdict\n",
    "import random\n",
    "import cv2\n",
    "import numpy as np\n",
    "from tqdm import tqdm\n",
    "\n",
    "# (!!!) 자주 바꿀 수 있는 글로벌 파라미터\n",
    "# 이미지들이 저장된 루트 경로 (하위 폴더 포함) – 반드시 절대경로로 지정\n",
    "COMMON_DIR = \"/ltb\"\n",
    "\n",
    "IMAGES_ROOT = COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4/images\"\n",
    "# 어노테이션 파일들이 저장된 경로\n",
    "ANNOTATIONS_DIR =  COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4/releases/dl4-mm-coco/annotations\"\n",
    "# 합쳐진 COCO JSON 파일을 저장할 경로\n",
    "OUTPUT_JSON_PATH =  COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4/mm_coco_annotations.json\"\n",
    "\n",
    "# train/val 분할 결과 JSON 파일 경로\n",
    "TRAIN_JSON = os.path.join( COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4\", \"train.json\")\n",
    "VAL_JSON = os.path.join( COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4\", \"val.json\")\n",
    "\n",
    "# 이미지 복사시 원본 이미지 디렉토리 (이미지 파일은 이 경로 하위에 존재)\n",
    "IMAGES_SRC_DIR = IMAGES_ROOT  # 이미 절대경로로 지정됨\n",
    "\n",
    "# 이미지 복사시 출력(복사된) 이미지가 저장될 폴더 (폴더 구조는 유지됨)\n",
    "TRAIN_IMAGES_DST_DIR = os.path.join( COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4\", \"train\", \"images\")\n",
    "VAL_IMAGES_DST_DIR   = os.path.join( COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4\", \"val\", \"images\")\n",
    "\n",
    "# 라벨링된 이미지(바운딩 박스가 그려진)를 저장할 폴더\n",
    "LABELED_IMAGES_FOLDER =  COMMON_DIR + \"/media/ltb/90887173887158A4/Users/ltb/si/dl-e-c-project_4/labeled_images_auto\"\n",
    "os.makedirs(LABELED_IMAGES_FOLDER, exist_ok=True)\n",
    "\n",
    "# 디버깅용 출력\n",
    "print(\"이미지 루트 경로:\", IMAGES_ROOT)\n",
    "print(\"Annotation 폴더:\", ANNOTATIONS_DIR)\n",
    "print(\"합친 JSON 파일:\", OUTPUT_JSON_PATH)\n",
    "print(\"Train JSON:\", TRAIN_JSON)\n",
    "print(\"Val JSON:\", VAL_JSON)\n",
    "print(\"Train 이미지 출력 폴더:\", TRAIN_IMAGES_DST_DIR)\n",
    "print(\"Val 이미지 출력 폴더:\", VAL_IMAGES_DST_DIR)\n",
    "print(\"라벨링 이미지 폴더:\", LABELED_IMAGES_FOLDER)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 2: 어노테이션 파일들을 합쳐 COCO JSON 생성]\n",
    "# 각 어노테이션 파일을 읽어서 COCO 포맷(이미지, annotation, category)으로 변환하는 셀입니다.\n",
    "coco_images = []       # 이미지 정보 리스트\n",
    "coco_annotations = []  # annotation 정보 리스트\n",
    "category_mapping = {}  # 카테고리 이름 → id 매핑 (동적 생성)\n",
    "next_category_id = 1\n",
    "next_image_id = 1\n",
    "next_anno_id = 1\n",
    "\n",
    "# ANNOTATIONS_DIR 내의 모든 JSON 파일 목록을 읽어옴\n",
    "annotation_files = glob.glob(os.path.join(ANNOTATIONS_DIR, \"*.json\"))\n",
    "print(f\"총 {len(annotation_files)}개의 어노테이션 파일을 찾았습니다.\")\n",
    "\n",
    "for anno_file in annotation_files:\n",
    "    try:\n",
    "        with open(anno_file, 'r', encoding='utf-8') as f:\n",
    "            data = json.load(f)\n",
    "    except Exception as e:\n",
    "        print(f\"파일 {anno_file} 로딩 중 오류 발생: {e}\")\n",
    "        continue\n",
    "\n",
    "    # 이미지 정보 추출 (item 내의 name, path 등)\n",
    "    item = data.get(\"item\", {})\n",
    "    image_name = item.get(\"name\", None)\n",
    "    if image_name is None:\n",
    "        print(f\"{anno_file}에 이미지 이름 정보 누락\")\n",
    "        continue\n",
    "\n",
    "    # image slot 탐색 ('image' 타입 우선)\n",
    "    image_slot = None\n",
    "    for slot in item.get(\"slots\", []):\n",
    "        if slot.get(\"type\", \"\") == \"image\":\n",
    "            image_slot = slot\n",
    "            break\n",
    "    if image_slot is None:\n",
    "        print(f\"{anno_file}에서 image slot을 찾을 수 없음\")\n",
    "        continue\n",
    "\n",
    "    width = image_slot.get(\"width\", None)\n",
    "    height = image_slot.get(\"height\", None)\n",
    "    if width is None or height is None:\n",
    "        print(f\"{anno_file}에서 width/height 정보 누락\")\n",
    "        continue\n",
    "\n",
    "    # 이미지 경로 구성\n",
    "    source_files = image_slot.get(\"source_files\", [])\n",
    "    local_path = \"\"\n",
    "    if len(source_files) > 0:\n",
    "        local_path = source_files[0].get(\"local_path\", \"\").strip()\n",
    "    \n",
    "    if not local_path:\n",
    "        relative_folder = item.get(\"path\", \"\").lstrip(\"/\")\n",
    "        local_path = os.path.join(IMAGES_ROOT, relative_folder, image_name)\n",
    "    else:\n",
    "        if not os.path.isabs(local_path):\n",
    "            local_path = os.path.join(IMAGES_ROOT, local_path)\n",
    "    \n",
    "    # 경로 보정: JSON 내 경로가 \"/root/...\"로 시작하면 \"/DATA2/ltb/root/...\"로 보정\n",
    "    if os.path.isabs(local_path) and not local_path.startswith(IMAGES_ROOT):\n",
    "        corrected_path = os.path.join(\"/DATA2/ltb\", local_path.lstrip(\"/\"))\n",
    "        print(f\"디버그: 보정 전: {local_path}\")\n",
    "        print(f\"디버그: 보정 후: {corrected_path}\")\n",
    "        local_path = corrected_path\n",
    "\n",
    "    if next_image_id <= 5:\n",
    "        print(f\"디버그: 이미지 {next_image_id} 최종 경로 -> {local_path}\")\n",
    "\n",
    "    coco_img = {\n",
    "        \"id\": next_image_id,\n",
    "        \"file_name\": local_path,\n",
    "        \"width\": width,\n",
    "        \"height\": height\n",
    "    }\n",
    "    coco_images.append(coco_img)\n",
    "\n",
    "    # annotation 처리\n",
    "    for anno in data.get(\"annotations\", []):\n",
    "        cat_name = anno.get(\"name\", \"undefined\")\n",
    "        if cat_name not in category_mapping:\n",
    "            category_mapping[cat_name] = next_category_id\n",
    "            next_category_id += 1\n",
    "\n",
    "        bb = anno.get(\"bounding_box\", {})\n",
    "        x = bb.get(\"x\", 0)\n",
    "        y = bb.get(\"y\", 0)\n",
    "        w = bb.get(\"w\", 0)\n",
    "        h = bb.get(\"h\", 0)\n",
    "        area = w * h\n",
    "\n",
    "        coco_anno = {\n",
    "            \"id\": next_anno_id,\n",
    "            \"image_id\": next_image_id,\n",
    "            \"category_id\": category_mapping[cat_name],\n",
    "            \"bbox\": [x, y, w, h],\n",
    "            \"area\": area,\n",
    "            \"iscrowd\": 0,\n",
    "            \"segmentation\": []\n",
    "        }\n",
    "        coco_annotations.append(coco_anno)\n",
    "        next_anno_id += 1\n",
    "\n",
    "    next_image_id += 1\n",
    "\n",
    "print(f\"총 {len(coco_images)}개의 이미지, {len(coco_annotations)}개의 annotation 처리됨.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 3: COCO 카테고리 목록 구성]\n",
    "# 지금까지 생성한 category_mapping을 바탕으로 COCO 형식의 카테고리 리스트를 생성합니다.\n",
    "coco_categories = []\n",
    "for cat_name, cat_id in category_mapping.items():\n",
    "    coco_categories.append({\n",
    "        \"id\": cat_id,\n",
    "        \"name\": cat_name,\n",
    "        \"supercategory\": cat_name\n",
    "    })\n",
    "\n",
    "print(\"발견된 카테고리:\")\n",
    "for cat in coco_categories:\n",
    "    print(cat)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 4: 최종 COCO JSON 생성 및 저장]\n",
    "# 이미지, annotation, 카테고리 정보를 하나의 COCO JSON 구조로 결합 후 저장\n",
    "coco_output = {\n",
    "    \"images\": coco_images,\n",
    "    \"annotations\": coco_annotations,\n",
    "    \"categories\": coco_categories\n",
    "}\n",
    "\n",
    "output_dir = os.path.dirname(OUTPUT_JSON_PATH)\n",
    "if not os.path.exists(output_dir):\n",
    "    os.makedirs(output_dir)\n",
    "    print(f\"출력 폴더 생성: {output_dir}\")\n",
    "\n",
    "with open(OUTPUT_JSON_PATH, 'w', encoding='utf-8') as f:\n",
    "    json.dump(coco_output, f, ensure_ascii=False, indent=2)\n",
    "\n",
    "print(\"COCO JSON 파일 저장 완료:\")\n",
    "print(OUTPUT_JSON_PATH)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 5: COCO JSON Train/Val 분할]\n",
    "# 아래 코드는 merge된 COCO JSON(OUTPUT_JSON_PATH)을 train/val로 분할하는 함수입니다.\n",
    "def split_coco_json(input_json, train_json, val_json, val_ratio=0.1, seed=42):\n",
    "    \"\"\"\n",
    "    COCO 포맷의 통합 JSON 파일을 train/val 데이터셋으로 분할하는 함수.\n",
    "    매개변수:\n",
    "      - input_json: 원본 COCO JSON 파일 경로\n",
    "      - train_json: 분할된 train JSON 파일 저장 경로\n",
    "      - val_json: 분할된 val JSON 파일 저장 경로\n",
    "      - val_ratio: 각 카테고리별로 val에 배정할 이미지 비율 (기본 0.1)\n",
    "      - seed: 무작위 분할을 위한 시드 값 (재현성을 위해)\n",
    "    \"\"\"\n",
    "    random.seed(seed)\n",
    "    \n",
    "    with open(input_json, 'r', encoding='utf-8') as f:\n",
    "        coco = json.load(f)\n",
    "    \n",
    "    annotations = coco.get('annotations', [])\n",
    "    images = coco.get('images', [])\n",
    "    categories = coco.get('categories', [])\n",
    "    \n",
    "    image_id_to_image = {img['id']: img for img in images}\n",
    "    \n",
    "    # 카테고리별 이미지 ID 수집\n",
    "    category_id_to_image_ids = defaultdict(set)\n",
    "    for ann in annotations:\n",
    "        image_id = ann.get('image_id')\n",
    "        category_id = ann.get('category_id')\n",
    "        if image_id is not None and category_id is not None:\n",
    "            category_id_to_image_ids[category_id].add(image_id)\n",
    "    \n",
    "    all_image_ids = set(image_id_to_image.keys())\n",
    "    train_image_ids = set()\n",
    "    val_image_ids = set()\n",
    "    \n",
    "    for category in categories:\n",
    "        cid = category.get('id')\n",
    "        image_ids = list(category_id_to_image_ids.get(cid, []))\n",
    "        random.shuffle(image_ids)\n",
    "        num_val = max(1, int(len(image_ids) * val_ratio))\n",
    "        val_ids = set(image_ids[:num_val])\n",
    "        train_ids = set(image_ids[num_val:])\n",
    "        val_image_ids.update(val_ids)\n",
    "        train_image_ids.update(train_ids)\n",
    "    \n",
    "    # 중복 제거\n",
    "    overlapping_ids = train_image_ids & val_image_ids\n",
    "    for img_id in overlapping_ids:\n",
    "        val_image_ids.remove(img_id)\n",
    "    \n",
    "    remaining_ids = all_image_ids - (train_image_ids | val_image_ids)\n",
    "    train_image_ids.update(remaining_ids)\n",
    "    \n",
    "    train_images = [image_id_to_image[iid] for iid in train_image_ids]\n",
    "    val_images = [image_id_to_image[iid] for iid in val_image_ids]\n",
    "    train_annotations = [ann for ann in annotations if ann.get('image_id') in train_image_ids]\n",
    "    val_annotations = [ann for ann in annotations if ann.get('image_id') in val_image_ids]\n",
    "    \n",
    "    train_coco = {\n",
    "        \"info\": coco.get(\"info\", {}),\n",
    "        \"licenses\": coco.get(\"licenses\", []),\n",
    "        \"images\": train_images,\n",
    "        \"annotations\": train_annotations,\n",
    "        \"categories\": categories\n",
    "    }\n",
    "    val_coco = {\n",
    "        \"info\": coco.get(\"info\", {}),\n",
    "        \"licenses\": coco.get(\"licenses\", []),\n",
    "        \"images\": val_images,\n",
    "        \"annotations\": val_annotations,\n",
    "        \"categories\": categories\n",
    "    }\n",
    "    \n",
    "    with open(train_json, 'w', encoding='utf-8') as f:\n",
    "        json.dump(train_coco, f, ensure_ascii=False, indent=4)\n",
    "    with open(val_json, 'w', encoding='utf-8') as f:\n",
    "        json.dump(val_coco, f, ensure_ascii=False, indent=4)\n",
    "    \n",
    "    print(f\"전체 이미지 수: {len(all_image_ids)}\")\n",
    "    print(f\"Train 이미지 수: {len(train_images)}\")\n",
    "    print(f\"Val 이미지 수: {len(val_images)}\")\n",
    "    \n",
    "    def print_category_counts(annotations, name):\n",
    "        counts = defaultdict(int)\n",
    "        for ann in annotations:\n",
    "            cid = ann.get('category_id')\n",
    "            if cid is not None:\n",
    "                counts[cid] += 1\n",
    "        print(f\"{name} annotation per category:\")\n",
    "        for cat in categories:\n",
    "            cid = cat.get('id')\n",
    "            print(f\"  Category {cid} ({cat.get('name', '')}): {counts.get(cid, 0)}\")\n",
    "    \n",
    "    print_category_counts(train_annotations, \"Train\")\n",
    "    print_category_counts(val_annotations, \"Val\")\n",
    "\n",
    "# 실행\n",
    "split_coco_json(OUTPUT_JSON_PATH, TRAIN_JSON, VAL_JSON, val_ratio=0.3, seed=54)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 6: 이미지 복사 함수 및 실행]\n",
    "# 이 셀은 train.json과 val.json에 기록된 이미지 파일들을 원본(IMAGES_SRC_DIR)에서\n",
    "# 각각 TRAIN_IMAGES_DST_DIR, VAL_IMAGES_DST_DIR로 병렬 복사하는 기능을 수행합니다.\n",
    "def copy_single_image(src_dst_tuple):\n",
    "    \"\"\"\n",
    "    단일 이미지 파일을 복사하는 함수.\n",
    "    Parameters:\n",
    "        src_dst_tuple (tuple): (소스 경로, 목적지 경로)\n",
    "    Returns:\n",
    "        에러 메시지 또는 None.\n",
    "    \"\"\"\n",
    "    src_path, dst_path = src_dst_tuple\n",
    "    try:\n",
    "        if not os.path.exists(src_path):\n",
    "            return f\"Warning: Source image does not exist: {src_path}\"\n",
    "        os.makedirs(os.path.dirname(dst_path), exist_ok=True)\n",
    "        if os.path.exists(dst_path):\n",
    "            return f\"Skipping already exists: {dst_path}\"\n",
    "        shutil.copy2(src_path, dst_path)\n",
    "        return None\n",
    "    except Exception as e:\n",
    "        return f\"Error copying {src_path} to {dst_path}: {e}\"\n",
    "\n",
    "def copy_images(json_path, images_src_dir, images_dst_dir, max_workers=8):\n",
    "    \"\"\"\n",
    "    JSON 파일에 명시된 이미지들을 원본에서 목적지로 병렬 복사하는 함수.\n",
    "    Parameters:\n",
    "        json_path (str): JSON 파일 경로.\n",
    "        images_src_dir (str): 원본 이미지 디렉토리.\n",
    "        images_dst_dir (str): 목적지 이미지 디렉토리.\n",
    "        max_workers (int): 최대 스레드 수.\n",
    "    \"\"\"\n",
    "    with open(json_path, 'r', encoding='utf-8') as f:\n",
    "        coco = json.load(f)\n",
    "    images = coco.get('images', [])\n",
    "    total = len(images)\n",
    "    print(f\"총 복사할 이미지 수: {total}\")\n",
    "    \n",
    "    src_dst_list = []\n",
    "    for img in images:\n",
    "        file_name = img.get('file_name', '')\n",
    "        if not file_name:\n",
    "            print(\"경고: 이미지에 file_name 정보 없음\")\n",
    "            continue\n",
    "        # file_name이 절대경로가 아니라면 base로 IMAGES_SRC_DIR와 결합\n",
    "        if not os.path.isabs(file_name):\n",
    "            corrected_file = os.path.join(IMAGES_SRC_DIR, file_name)\n",
    "        else:\n",
    "            # file_name이 절대경로지만 '/root/...'라면 보정\n",
    "            if not file_name.startswith(IMAGES_ROOT):\n",
    "                corrected_file = os.path.join(\"/DATA2/ltb\", file_name.lstrip(\"/\"))\n",
    "            else:\n",
    "                corrected_file = file_name\n",
    "        src_path = corrected_file\n",
    "        try:\n",
    "            relative_path = os.path.relpath(src_path, IMAGES_ROOT)\n",
    "        except Exception as e:\n",
    "            print(f\"에러: relpath 계산 실패: {src_path} ({e})\")\n",
    "            relative_path = os.path.basename(src_path)\n",
    "        dst_path = os.path.join(images_dst_dir, relative_path)\n",
    "        src_dst_list.append((src_path, dst_path))\n",
    "    \n",
    "    copied = 0; skipped = 0\n",
    "    missing_files = []; errors = []\n",
    "    with ThreadPoolExecutor(max_workers=max_workers) as executor:\n",
    "        futures = {executor.submit(copy_single_image, paths): paths for paths in src_dst_list}\n",
    "        for idx, future in enumerate(as_completed(futures), 1):\n",
    "            result = future.result()\n",
    "            if result:\n",
    "                if result.startswith(\"Warning\"):\n",
    "                    missing_files.append(result)\n",
    "                elif result.startswith(\"Skipping\"):\n",
    "                    skipped += 1\n",
    "                else:\n",
    "                    errors.append(result)\n",
    "            else:\n",
    "                copied += 1\n",
    "            if idx % 100 == 0 or idx == total:\n",
    "                print(f\"진행: {idx}/{total} 이미지 처리됨\")\n",
    "    \n",
    "    print(\"\\n=== 복사 요약 ===\")\n",
    "    print(f\"총 이미지: {total}\")\n",
    "    print(f\"복사된 이미지: {copied}\")\n",
    "    print(f\"건너뛴 이미지: {skipped}\")\n",
    "    print(f\"누락된 이미지: {len(missing_files)}\")\n",
    "    if missing_files:\n",
    "        print(\"누락된 이미지 목록:\")\n",
    "        for warn in missing_files:\n",
    "            print(\"  -\", warn)\n",
    "    if errors:\n",
    "        print(\"에러 발생 파일:\")\n",
    "        for err in errors:\n",
    "            print(\"  -\", err)\n",
    "\n",
    "print(\"=== Train 이미지 복사 시작 ===\")\n",
    "copy_images(TRAIN_JSON, IMAGES_SRC_DIR, TRAIN_IMAGES_DST_DIR, max_workers=16)\n",
    "print(\"=== Train 이미지 복사 완료 ===\\n\")\n",
    "\n",
    "print(\"=== Val 이미지 복사 시작 ===\")\n",
    "copy_images(VAL_JSON, IMAGES_SRC_DIR, VAL_IMAGES_DST_DIR, max_workers=16)\n",
    "print(\"=== Val 이미지 복사 완료 ===\\n\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [Cell 7: 라벨링된 이미지 생성 (바운딩 박스 그리기)]\n",
    "# 이 셀은 주어진 JSON(예: val.json)의 이미지에 대해,\n",
    "# 바운딩 박스와 클래스 레이블을 그린 후, 출력 폴더(LABELED_IMAGES_FOLDER)에 저장하는 기능을 수행합니다.\n",
    "# 만약 JSON의 \"file_name\" 필드가 절대경로이면 base_folder_path는 비워둡니다.\n",
    "json_file_path = VAL_JSON  # 예: val.json 경로 사용\n",
    "base_folder_path = \"\"       # file_name이 절대경로일 경우 비워둠\n",
    "\n",
    "with open(json_file_path, 'r', encoding='utf-8') as f:\n",
    "    data = json.load(f)\n",
    "\n",
    "# 카테고리 ID와 이름 매핑\n",
    "category_id_to_name = {cat['id']: cat['name'] for cat in data['categories']}\n",
    "\n",
    "# 클래스별 색상 할당 (deterministic)\n",
    "def generate_color(category_id):\n",
    "    r = (category_id * 37) % 256\n",
    "    g = (category_id * 73) % 256\n",
    "    b = (category_id * 17) % 256\n",
    "    return (r, g, b)\n",
    "category_colors = {cid: generate_color(cid) for cid in category_id_to_name.keys()}\n",
    "\n",
    "# 이미지 ID와 파일 경로 매핑\n",
    "image_id_to_file = {img['id']: img['file_name'] for img in data['images']}\n",
    "total_images = len(data['images'])\n",
    "print(f\"총 라벨링할 이미지 수: {total_images}\")\n",
    "\n",
    "with tqdm(total=total_images, desc=\"이미지 처리 중\") as pbar:\n",
    "    for image in data['images']:\n",
    "        image_id = image['id']\n",
    "        file_name = image['file_name']\n",
    "        if not os.path.isabs(file_name):\n",
    "            image_path = os.path.join(base_folder_path, file_name)\n",
    "        else:\n",
    "            image_path = file_name\n",
    "        img = cv2.imread(image_path)\n",
    "        if img is None:\n",
    "            print(f\"이미지 로드 실패: {image_path}\")\n",
    "            pbar.update(1)\n",
    "            continue\n",
    "        # 해당 이미지의 모든 annotation 가져오기\n",
    "        annotations = [ann for ann in data['annotations'] if ann['image_id'] == image_id]\n",
    "        for ann in annotations:\n",
    "            bbox = ann['bbox']  # [x, y, w, h]\n",
    "            category_id = ann['category_id']\n",
    "            category_name = category_id_to_name.get(category_id, \"Unknown\")\n",
    "            color = category_colors.get(category_id, (0, 0, 255))\n",
    "            x, y, w, h = map(int, bbox)\n",
    "            cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)\n",
    "            label = category_name\n",
    "            label_size, baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)\n",
    "            label_x = x\n",
    "            label_y = y - 10 if y - 10 > 10 else y + 10\n",
    "            cv2.rectangle(img, (label_x, label_y - label_size[1] - baseline),\n",
    "                          (label_x + label_size[0], label_y + baseline), color, -1)\n",
    "            cv2.putText(img, label, (label_x, label_y), cv2.FONT_HERSHEY_SIMPLEX,\n",
    "                        0.5, (255,255,255), 1)\n",
    "        # 파일명에 '/'가 있으면 '_'로 대체\n",
    "        output_image_path = os.path.join(LABELED_IMAGES_FOLDER, file_name.replace(\"/\", \"_\"))\n",
    "        os.makedirs(os.path.dirname(output_image_path), exist_ok=True)\n",
    "        cv2.imwrite(output_image_path, img)\n",
    "        pbar.update(1)\n",
    "\n",
    "print(\"라벨링된 이미지 저장 완료:\", LABELED_IMAGES_FOLDER)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "ltb_mmyolo",
   "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.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
