{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d4b4150e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/cuuva/anaconda3/envs/1stagedetect/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n",
      "Generating train split: 100%|██████████| 45623/45623 [00:03<00:00, 12408.24 examples/s]\n",
      "Generating val split: 100%|██████████| 1158/1158 [00:00<00:00, 9773.25 examples/s] \n"
     ]
    }
   ],
   "source": [
    "from datasets import load_dataset\n",
    "\n",
    "ds = load_dataset(\"detection-datasets/fashionpedia\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cd53e76f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DatasetDict({\n",
      "    train: Dataset({\n",
      "        features: ['image_id', 'image', 'width', 'height', 'objects'],\n",
      "        num_rows: 45623\n",
      "    })\n",
      "    val: Dataset({\n",
      "        features: ['image_id', 'image', 'width', 'height', 'objects'],\n",
      "        num_rows: 1158\n",
      "    })\n",
      "})\n"
     ]
    }
   ],
   "source": [
    "print(ds)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd95eafe",
   "metadata": {},
   "source": [
    "## YOLO format으로 변경"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "fa6c4f7f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📥 Hugging Face에서 데이터셋 다운로드 중...\n",
      "📋 총 46개의 클래스가 감지되었습니다.\n",
      "   예시: ['shirt, blouse', 'top, t-shirt, sweatshirt', 'sweater', 'cardigan', 'jacket'] ...\n",
      "🚀 train 데이터 처리 중... (데이터가 많으니 조금 걸릴 수 있어!)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45623/45623 [01:48<00:00, 422.31it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🚀 val 데이터 처리 중... (데이터가 많으니 조금 걸릴 수 있어!)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1158/1158 [00:02<00:00, 440.09it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✅ 모든 준비 완료! YAML 파일 경로: /home/cuuva/experiment/datasets/fashionpedia_yolo/fashionpedia.yaml\n",
      "📂 데이터 저장 위치: /home/cuuva/experiment/datasets/fashionpedia_yolo\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from pathlib import Path\n",
    "from datasets import load_dataset\n",
    "from tqdm import tqdm\n",
    "import yaml\n",
    "\n",
    "# 1. 데이터셋 로드\n",
    "print(\"📥 Hugging Face에서 데이터셋 다운로드 중...\")\n",
    "ds = load_dataset(\"detection-datasets/fashionpedia\")\n",
    "\n",
    "# 2. 저장할 경로 설정 (네가 원하는 로컬 경로로 수정!)\n",
    "base_path = Path(\"/home/cuuva/experiment/datasets/fashionpedia_yolo\")\n",
    "images_path = base_path / \"images\"\n",
    "labels_path = base_path / \"labels\"\n",
    "\n",
    "# 폴더 생성 함수\n",
    "def create_dir_structure():\n",
    "    for split in ['train', 'val']:\n",
    "        (images_path / split).mkdir(parents=True, exist_ok=True)\n",
    "        (labels_path / split).mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "create_dir_structure()\n",
    "\n",
    "# ------------------------------------------------------------------\n",
    "# 3. [수정됨] 클래스 이름 추출\n",
    "# 너의 디버깅 정보: {'category': List(ClassLabel(names=['shirt, ...'])), ...}\n",
    "# 따라서 ['objects']['category']로 접근 후 .feature.names를 가져옵니다.\n",
    "# ------------------------------------------------------------------\n",
    "try:\n",
    "    # 데이터셋 구조에 따라 접근 방식이 조금 다를 수 있어 안전하게 처리\n",
    "    categories_feature = ds['train'].features['objects']['category']\n",
    "    \n",
    "    # 만약 Sequence(ClassLabel) 형태라면 .feature를 통해 접근\n",
    "    if hasattr(categories_feature, 'feature'):\n",
    "        categories = categories_feature.feature.names\n",
    "    else:\n",
    "        # 혹시 바로 ClassLabel인 경우\n",
    "        categories = categories_feature.names\n",
    "        \n",
    "    print(f\"📋 총 {len(categories)}개의 클래스가 감지되었습니다.\")\n",
    "    print(f\"   예시: {categories[:5]} ...\")\n",
    "    \n",
    "except Exception as e:\n",
    "    print(f\"❌ 카테고리 추출 중 에러 발생: {e}\")\n",
    "    # 만약 자동 추출이 실패하면, 네가 보내준 로그를 바탕으로 하드코딩된 리스트를 사용해도 돼.\n",
    "    print(\"⚠️ 수동으로 카테고리 리스트를 설정합니다.\")\n",
    "    categories = ['shirt, blouse', 'top, t-shirt, sweatshirt', 'sweater', 'cardigan', 'jacket', 'vest', 'pants', 'shorts', 'skirt', 'coat', 'dress', 'jumpsuit', 'cape', 'glasses', 'hat', 'headband, head covering, hair accessory', 'tie', 'glove', 'watch', 'belt', 'leg warmer', 'tights, stockings', 'sock', 'shoe', 'bag, wallet', 'scarf', 'umbrella', 'hood', 'collar', 'lapel', 'epaulette', 'sleeve', 'pocket', 'neckline', 'buckle', 'zipper', 'applique', 'bead', 'bow', 'flower', 'fringe', 'ribbon', 'rivet', 'ruffle', 'sequin', 'tassel']\n",
    "\n",
    "# 4. 변환 함수 정의 (BBox: XYXY -> YOLO Normalized XYWH)\n",
    "def convert_bbox(bbox, img_width, img_height):\n",
    "    # 네가 보여준 데이터: [445.0, 910.0, 505.0, 983.0] -> x_min, y_min, x_max, y_max (절대좌표)\n",
    "    min_x, min_y, max_x, max_y = bbox\n",
    "    \n",
    "    # 너비와 높이 계산\n",
    "    dw = 1. / img_width\n",
    "    dh = 1. / img_height\n",
    "    \n",
    "    w = max_x - min_x\n",
    "    h = max_y - min_y\n",
    "    x_center = min_x + (w / 2)\n",
    "    y_center = min_y + (h / 2)\n",
    "    \n",
    "    # 정규화 (0~1 사이 값)\n",
    "    x_center *= dw\n",
    "    w *= dw\n",
    "    y_center *= dh\n",
    "    h *= dh\n",
    "    \n",
    "    return x_center, y_center, w, h\n",
    "\n",
    "# 5. 데이터 처리 및 저장 루프\n",
    "def process_dataset(dataset_split, split_name):\n",
    "    print(f\"🚀 {split_name} 데이터 처리 중... (데이터가 많으니 조금 걸릴 수 있어!)\")\n",
    "    \n",
    "    for item in tqdm(dataset_split):\n",
    "        image = item['image']\n",
    "        image_id = item['image_id']\n",
    "        objects = item['objects']\n",
    "        \n",
    "        # 이미지 크기\n",
    "        img_w, img_h = image.size\n",
    "        \n",
    "        # 파일 이름 설정\n",
    "        file_name = f\"{image_id}\"\n",
    "        \n",
    "        # 1) 이미지 저장 (RGB 변환 필수)\n",
    "        # 이미 존재하면 건너뛰는 로직을 추가하고 싶으면 아래 주석 해제\n",
    "        # if (images_path / split_name / f\"{file_name}.jpg\").exists(): continue\n",
    "        \n",
    "        image.convert(\"RGB\").save(images_path / split_name / f\"{file_name}.jpg\")\n",
    "        \n",
    "        # 2) 라벨 저장\n",
    "        label_file = labels_path / split_name / f\"{file_name}.txt\"\n",
    "        \n",
    "        with open(label_file, \"w\") as f:\n",
    "            # 한 이미지 내의 여러 객체(objects) 루프\n",
    "            for i in range(len(objects['category'])):\n",
    "                category_id = objects['category'][i]\n",
    "                bbox = objects['bbox'][i]\n",
    "                \n",
    "                # 좌표 변환\n",
    "                x_c, y_c, w, h = convert_bbox(bbox, img_w, img_h)\n",
    "                \n",
    "                # YOLO 포맷 작성: class_id x_c y_c w h\n",
    "                f.write(f\"{category_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}\\n\")\n",
    "\n",
    "# 실제 변환 실행\n",
    "process_dataset(ds['train'], 'train')\n",
    "process_dataset(ds['val'], 'val')\n",
    "\n",
    "# 6. yaml 파일 생성\n",
    "yaml_content = {\n",
    "    'path': str(base_path),\n",
    "    'train': 'images/train',\n",
    "    'val': 'images/val',\n",
    "    'names': {i: name for i, name in enumerate(categories)}\n",
    "}\n",
    "\n",
    "yaml_path = base_path / \"fashionpedia.yaml\"\n",
    "with open(yaml_path, 'w') as f:\n",
    "    yaml.dump(yaml_content, f, sort_keys=False)\n",
    "\n",
    "print(f\"\\n✅ 모든 준비 완료! YAML 파일 경로: {yaml_path}\")\n",
    "print(f\"📂 데이터 저장 위치: {base_path}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "55766887",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[TRAIN 데이터셋 분석]\n",
      "총 파일 개수: 45620개\n",
      "Class 0: 6161개\n",
      "Class 1: 16548개\n",
      "Class 2: 1494개\n",
      "Class 3: 1107개\n",
      "Class 4: 7833개\n",
      "Class 5: 719개\n",
      "Class 6: 12414개\n",
      "Class 7: 2756개\n",
      "Class 8: 5046개\n",
      "Class 9: 3124개\n",
      "Class 10: 19813개\n",
      "Class 11: 4855개\n",
      "Class 12: 2518개\n",
      "Class 13: 46374개\n",
      "Class 14: 7217개\n",
      "Class 15: 1374개\n",
      "------------------------------\n",
      "[VAL 데이터셋 분석]\n",
      "총 파일 개수: 1158개\n",
      "Class 0: 102개\n",
      "Class 1: 477개\n",
      "Class 2: 21개\n",
      "Class 3: 12개\n",
      "Class 4: 183개\n",
      "Class 5: 22개\n",
      "Class 6: 314개\n",
      "Class 7: 106개\n",
      "Class 8: 162개\n",
      "Class 9: 104개\n",
      "Class 10: 534개\n",
      "Class 11: 130개\n",
      "Class 12: 74개\n",
      "Class 13: 1566개\n",
      "Class 14: 214개\n",
      "Class 15: 48개\n",
      "------------------------------\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import glob\n",
    "from collections import Counter\n",
    "\n",
    "# 경로 설정\n",
    "base_dir = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo/labels\"\n",
    "targets = ['train', 'val']\n",
    "\n",
    "for target in targets:\n",
    "    target_path = os.path.join(base_dir, target)\n",
    "    txt_files = glob.glob(os.path.join(target_path, \"*.txt\"))\n",
    "    \n",
    "    print(f\"[{target.upper()} 데이터셋 분석]\")\n",
    "    print(f\"총 파일 개수: {len(txt_files)}개\")\n",
    "    \n",
    "    # 카운터 초기화\n",
    "    cls_counter = Counter()\n",
    "    \n",
    "    for file in txt_files:\n",
    "        with open(file, 'r') as f:\n",
    "            lines = f.readlines()\n",
    "            for line in lines:\n",
    "                data = line.strip().split()\n",
    "                if data:\n",
    "                    # 첫 번째 숫자가 클래스 ID\n",
    "                    cls_id = int(data[0])\n",
    "                    cls_counter[cls_id] += 1\n",
    "    \n",
    "    # 결과 출력 (클래스 ID 순서대로 정렬)\n",
    "    # 데이터가 없는 경우 처리\n",
    "    if not cls_counter:\n",
    "        print(\"  라벨 데이터가 없습니다.\")\n",
    "    else:\n",
    "        for cls_id in sorted(cls_counter.keys()):\n",
    "            count = cls_counter[cls_id]\n",
    "            print(f\"Class {cls_id}: {count}개\")\n",
    "            \n",
    "    print(\"-\" * 30) # 구분선"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e11adde",
   "metadata": {},
   "source": [
    "## Case 1 : class 균등하게 \n",
    "## Case 2 : target class 비율 더 많이\n",
    "## Case 3 : target class만 구성 (근데 원하는 대로 안됐음)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3acc393e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 데이터 로딩 및 가로형 이미지 필터링 중...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/45620 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45620/45620 [00:00<00:00, 56131.48it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[필터링 결과]\n",
      "- 세로형(Vertical) 제외됨: 40359장\n",
      "- 이미지 없음 제외됨: 0장\n",
      "- 최종 유효(Landscape) 데이터 개수: 5261장\n",
      "\n",
      "==================== Case 1: Uniform ====================\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/1_uniform_300 (300장)\n",
      "\n",
      "📊 [Uniform] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "0          | 56             \n",
      "1          | 121             (*Target)\n",
      "2          | 50              (*Target)\n",
      "3          | 50              (*Target)\n",
      "4          | 51              (*Target)\n",
      "5          | 50             \n",
      "6          | 81              (*Target)\n",
      "7          | 50             \n",
      "8          | 50             \n",
      "9          | 50             \n",
      "10         | 51             \n",
      "11         | 52              (*Target)\n",
      "12         | 57             \n",
      "13         | 113             (*Target)\n",
      "14         | 74             \n",
      "15         | 49             \n",
      "----------------------------------------\n",
      "Total Objects: 1005\n",
      "\n",
      "\n",
      "==================== Case 2: Target Bias ====================\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/2_target_bias_300 (300장)\n",
      "\n",
      "📊 [Target Bias] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "0          | 53             \n",
      "1          | 128             (*Target)\n",
      "2          | 14              (*Target)\n",
      "3          | 12              (*Target)\n",
      "4          | 48              (*Target)\n",
      "5          | 8              \n",
      "6          | 74              (*Target)\n",
      "7          | 35             \n",
      "8          | 27             \n",
      "9          | 24             \n",
      "10         | 99             \n",
      "11         | 50              (*Target)\n",
      "12         | 31             \n",
      "13         | 106             (*Target)\n",
      "14         | 44             \n",
      "15         | 12             \n",
      "----------------------------------------\n",
      "Total Objects: 765\n",
      "\n",
      "\n",
      "==================== Case 3: Target Only ====================\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/3_target_only_300 (300장)\n",
      "\n",
      "📊 [Target Only] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "0          | 39             \n",
      "1          | 186             (*Target)\n",
      "2          | 12              (*Target)\n",
      "3          | 5               (*Target)\n",
      "4          | 65              (*Target)\n",
      "5          | 5              \n",
      "6          | 91              (*Target)\n",
      "7          | 48             \n",
      "8          | 45             \n",
      "9          | 22             \n",
      "10         | 64             \n",
      "11         | 44              (*Target)\n",
      "12         | 31             \n",
      "13         | 155             (*Target)\n",
      "14         | 63             \n",
      "15         | 12             \n",
      "----------------------------------------\n",
      "Total Objects: 887\n",
      "\n",
      "모든 작업 완료!\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from collections import Counter, defaultdict\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 경로 설정\n",
    "LABEL_DIR = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo/labels/train\"\n",
    "IMAGE_DIR = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo/images/train\"\n",
    "OUTPUT_ROOT = \"/home/cuuva/다운로드/quantization_experiments\"\n",
    "\n",
    "# 타겟 클래스\n",
    "TARGET_CLASSES = {1, 2, 3, 4, 6, 11, 13}\n",
    "TOTAL_COUNT = 300\n",
    "\n",
    "# ================= 1. 데이터 로드 및 필터링 (가로형 이미지 체크) =================\n",
    "print(\"1. 데이터 로딩 및 가로형 이미지 필터링 중...\")\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "\n",
    "# 이미지 파일명(확장자 제외) -> 포함된 클래스 리스트 매핑\n",
    "file_class_map = {}\n",
    "class_file_map = defaultdict(list)\n",
    "\n",
    "skipped_vertical = 0\n",
    "skipped_no_image = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [Step A] 이미지 파일 찾기 및 해상도 체크\n",
    "    image_found = False\n",
    "    is_landscape = False\n",
    "    \n",
    "    found_img_path = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp_path = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp_path):\n",
    "            found_img_path = temp_path\n",
    "            image_found = True\n",
    "            break\n",
    "    \n",
    "    if not image_found:\n",
    "        skipped_no_image += 1\n",
    "        continue\n",
    "\n",
    "    try:\n",
    "        with Image.open(found_img_path) as img:\n",
    "            w, h = img.size\n",
    "            if w > h: # 가로가 더 긴 경우만\n",
    "                is_landscape = True\n",
    "            else:\n",
    "                skipped_vertical += 1\n",
    "    except Exception as e:\n",
    "        print(f\"Error reading image {basename}: {e}\")\n",
    "        continue\n",
    "\n",
    "    if not is_landscape:\n",
    "        continue\n",
    "\n",
    "    # [Step B] 라벨 정보 파싱\n",
    "    classes_in_file = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                cls_id = int(parts[0])\n",
    "                classes_in_file.add(cls_id)\n",
    "    \n",
    "    if classes_in_file:\n",
    "        file_class_map[basename] = list(classes_in_file)\n",
    "        for c in classes_in_file:\n",
    "            class_file_map[c].append(basename)\n",
    "\n",
    "all_files = list(file_class_map.keys())\n",
    "print(f\"\\n[필터링 결과]\")\n",
    "print(f\"- 세로형(Vertical) 제외됨: {skipped_vertical}장\")\n",
    "print(f\"- 이미지 없음 제외됨: {skipped_no_image}장\")\n",
    "print(f\"- 최종 유효(Landscape) 데이터 개수: {len(all_files)}장\")\n",
    "\n",
    "\n",
    "# ================= 2. 공통 함수 정의 =================\n",
    "\n",
    "def copy_files(selected_basenames, save_folder_name):\n",
    "    \"\"\"선택된 파일들을 지정된 폴더로 복사 (이미지만)\"\"\"\n",
    "    save_path = os.path.join(OUTPUT_ROOT, save_folder_name)\n",
    "    if os.path.exists(save_path):\n",
    "        shutil.rmtree(save_path)\n",
    "    os.makedirs(save_path, exist_ok=True)\n",
    "    \n",
    "    count = 0\n",
    "    for basename in selected_basenames:\n",
    "        for ext in ['.jpg', '.jpeg', '.png']:\n",
    "            src_img = os.path.join(IMAGE_DIR, basename + ext)\n",
    "            if os.path.exists(src_img):\n",
    "                shutil.copy(src_img, os.path.join(save_path, basename + ext))\n",
    "                count += 1\n",
    "                break\n",
    "    print(f\"-> 저장 완료: {save_path} ({count}장)\")\n",
    "\n",
    "def print_class_stats(selected_basenames, case_name):\n",
    "    \"\"\"선택된 파일들에 포함된 클래스 개수 통계 출력\"\"\"\n",
    "    stats = Counter()\n",
    "    for basename in selected_basenames:\n",
    "        # 해당 파일에 들어있는 클래스들을 모두 카운트\n",
    "        for cls_id in file_class_map[basename]:\n",
    "            stats[cls_id] += 1\n",
    "            \n",
    "    print(f\"\\n📊 [{case_name}] 클래스 분포 통계 (이미지 {len(selected_basenames)}장)\")\n",
    "    print(\"-\" * 40)\n",
    "    print(f\"{'Class ID':<10} | {'Count (Objects)':<15}\")\n",
    "    print(\"-\" * 40)\n",
    "    \n",
    "    # 클래스 ID 순으로 정렬하여 출력\n",
    "    sorted_stats = sorted(stats.items())\n",
    "    for cls_id, count in sorted_stats:\n",
    "        mark = \" (*Target)\" if cls_id in TARGET_CLASSES else \"\"\n",
    "        print(f\"{cls_id:<10} | {count:<15}{mark}\")\n",
    "    print(\"-\" * 40)\n",
    "    print(f\"Total Objects: {sum(stats.values())}\\n\")\n",
    "\n",
    "\n",
    "# ================= 3. 실험별 데이터 추출 =================\n",
    "\n",
    "# --- Case 1: Uniform (최대한 골고루) ---\n",
    "print(f\"\\n{'='*20} Case 1: Uniform {'='*20}\")\n",
    "selected_uniform = set()\n",
    "current_counts = Counter()\n",
    "temp_pool = all_files[:]\n",
    "random.shuffle(temp_pool)\n",
    "\n",
    "while len(selected_uniform) < TOTAL_COUNT and temp_pool:\n",
    "    counts_list = [(c, current_counts[c]) for c in range(16)]\n",
    "    counts_list.sort(key=lambda x: x[1])\n",
    "    target_cls = counts_list[0][0]\n",
    "    \n",
    "    found_img = None\n",
    "    candidates = [f for f in class_file_map[target_cls] if f not in selected_uniform]\n",
    "    \n",
    "    if candidates:\n",
    "        found_img = random.choice(candidates)\n",
    "    \n",
    "    if found_img is None:\n",
    "        remaining = [f for f in temp_pool if f not in selected_uniform]\n",
    "        if remaining:\n",
    "            found_img = remaining[0]\n",
    "        else:\n",
    "            break\n",
    "    \n",
    "    if found_img:\n",
    "        selected_uniform.add(found_img)\n",
    "        for c in file_class_map[found_img]:\n",
    "            current_counts[c] += 1\n",
    "\n",
    "copy_files(selected_uniform, \"1_uniform_300\")\n",
    "print_class_stats(selected_uniform, \"Uniform\")\n",
    "\n",
    "\n",
    "# --- Case 2: Target Bias (타겟 위주 + 나머지 조금) ---\n",
    "print(f\"\\n{'='*20} Case 2: Target Bias {'='*20}\")\n",
    "bias_target_count = 250\n",
    "bias_other_count = 50\n",
    "\n",
    "target_files = []\n",
    "other_files = []\n",
    "\n",
    "for fname in all_files:\n",
    "    classes = file_class_map[fname]\n",
    "    if not set(classes).isdisjoint(TARGET_CLASSES):\n",
    "        target_files.append(fname)\n",
    "    else:\n",
    "        other_files.append(fname)\n",
    "\n",
    "selected_bias = []\n",
    "if len(target_files) >= bias_target_count:\n",
    "    selected_bias.extend(random.sample(target_files, bias_target_count))\n",
    "else:\n",
    "    selected_bias.extend(target_files)\n",
    "\n",
    "remaining_needed = TOTAL_COUNT - len(selected_bias)\n",
    "if len(other_files) >= remaining_needed:\n",
    "    selected_bias.extend(random.sample(other_files, remaining_needed))\n",
    "else:\n",
    "    selected_bias.extend(other_files)\n",
    "\n",
    "copy_files(selected_bias, \"2_target_bias_300\")\n",
    "print_class_stats(selected_bias, \"Target Bias\")\n",
    "\n",
    "\n",
    "# --- Case 3: Target Only (타겟 클래스만) ---\n",
    "print(f\"\\n{'='*20} Case 3: Target Only {'='*20}\")\n",
    "\n",
    "selected_only = []\n",
    "target_files_case3 = [f for f in all_files if not set(file_class_map[f]).isdisjoint(TARGET_CLASSES)]\n",
    "\n",
    "if len(target_files_case3) >= TOTAL_COUNT:\n",
    "    selected_only = random.sample(target_files_case3, TOTAL_COUNT)\n",
    "else:\n",
    "    selected_only = target_files_case3\n",
    "    print(f\"Warning: 타겟 포함 이미지가 {len(target_files_case3)}장 뿐입니다.\")\n",
    "\n",
    "copy_files(selected_only, \"3_target_only_300\")\n",
    "print_class_stats(selected_only, \"Target Only\")\n",
    "\n",
    "print(\"모든 작업 완료!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28192213",
   "metadata": {},
   "source": [
    "## Case 3는 target class만 존재하도록 다시 설정해서 진행"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3dfcbad5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 데이터 로딩 및 가로형 이미지 필터링 중...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45620/45620 [00:00<00:00, 56942.68it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[필터링 결과]\n",
      "- 세로형(Vertical) 제외됨: 40359장\n",
      "- 이미지 없음 제외됨: 0장\n",
      "- 최종 유효(Landscape) 데이터 개수: 5261장\n",
      "\n",
      "==================== Case 1: Uniform ====================\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/1_uniform_300 (300장)\n",
      "\n",
      "📊 [Uniform] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "0          | 53              (Warning: Non-Target)\n",
      "1          | 116             (*Target)\n",
      "2          | 52              (*Target)\n",
      "3          | 52              (*Target)\n",
      "4          | 55              (*Target)\n",
      "5          | 52              (Warning: Non-Target)\n",
      "6          | 85              (*Target)\n",
      "7          | 52              (Warning: Non-Target)\n",
      "8          | 52              (Warning: Non-Target)\n",
      "9          | 52              (Warning: Non-Target)\n",
      "10         | 54              (Warning: Non-Target)\n",
      "11         | 56              (*Target)\n",
      "12         | 55              (Warning: Non-Target)\n",
      "13         | 121             (*Target)\n",
      "14         | 71              (Warning: Non-Target)\n",
      "15         | 52              (Warning: Non-Target)\n",
      "----------------------------------------\n",
      "Total Objects: 1030\n",
      "\n",
      "\n",
      "==================== Case 2: Target Bias ====================\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/2_target_bias_300 (300장)\n",
      "\n",
      "📊 [Target Bias] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "0          | 34              (Warning: Non-Target)\n",
      "1          | 129             (*Target)\n",
      "2          | 12              (*Target)\n",
      "3          | 7               (*Target)\n",
      "4          | 50              (*Target)\n",
      "5          | 5               (Warning: Non-Target)\n",
      "6          | 73              (*Target)\n",
      "7          | 35              (Warning: Non-Target)\n",
      "8          | 30              (Warning: Non-Target)\n",
      "9          | 20              (Warning: Non-Target)\n",
      "10         | 102             (Warning: Non-Target)\n",
      "11         | 39              (*Target)\n",
      "12         | 29              (Warning: Non-Target)\n",
      "13         | 112             (*Target)\n",
      "14         | 53              (Warning: Non-Target)\n",
      "15         | 11              (Warning: Non-Target)\n",
      "----------------------------------------\n",
      "Total Objects: 741\n",
      "\n",
      "\n",
      "==================== Case 3: Pure Target Only ====================\n",
      "조건을 만족하는(Target Only) 이미지는 총 1118장 입니다.\n",
      "-> 저장 완료: /home/cuuva/다운로드/quantization_experiments/3_target_only_pure (300장)\n",
      "\n",
      "📊 [Pure Target Only] 클래스 분포 통계 (이미지 300장)\n",
      "----------------------------------------\n",
      "Class ID   | Count (Objects)\n",
      "----------------------------------------\n",
      "1          | 243             (*Target)\n",
      "2          | 25              (*Target)\n",
      "3          | 12              (*Target)\n",
      "4          | 67              (*Target)\n",
      "6          | 148             (*Target)\n",
      "11         | 49              (*Target)\n",
      "13         | 74              (*Target)\n",
      "----------------------------------------\n",
      "Total Objects: 618\n",
      "\n",
      "모든 작업 완료!\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from collections import Counter, defaultdict\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 경로 설정\n",
    "LABEL_DIR = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo/labels/train\"\n",
    "IMAGE_DIR = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo/images/train\"\n",
    "OUTPUT_ROOT = \"/home/cuuva/다운로드/quantization_experiments\"\n",
    "\n",
    "# 타겟 클래스 (원하는 클래스만 남기고 나머지는 배제할 클래스들)\n",
    "TARGET_CLASSES = {1, 2, 3, 4, 6, 11, 13}\n",
    "TOTAL_COUNT = 300\n",
    "\n",
    "# ================= 1. 데이터 로드 및 필터링 (가로형 이미지 체크) =================\n",
    "print(\"1. 데이터 로딩 및 가로형 이미지 필터링 중...\")\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "\n",
    "# 이미지 파일명(확장자 제외) -> 포함된 클래스 리스트 매핑\n",
    "file_class_map = {}\n",
    "class_file_map = defaultdict(list)\n",
    "\n",
    "skipped_vertical = 0\n",
    "skipped_no_image = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [Step A] 이미지 파일 찾기 및 해상도 체크\n",
    "    image_found = False\n",
    "    is_landscape = False\n",
    "    \n",
    "    found_img_path = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp_path = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp_path):\n",
    "            found_img_path = temp_path\n",
    "            image_found = True\n",
    "            break\n",
    "    \n",
    "    if not image_found:\n",
    "        skipped_no_image += 1\n",
    "        continue\n",
    "\n",
    "    try:\n",
    "        with Image.open(found_img_path) as img:\n",
    "            w, h = img.size\n",
    "            if w > h: # 가로가 더 긴 경우만\n",
    "                is_landscape = True\n",
    "            else:\n",
    "                skipped_vertical += 1\n",
    "    except Exception as e:\n",
    "        print(f\"Error reading image {basename}: {e}\")\n",
    "        continue\n",
    "\n",
    "    if not is_landscape:\n",
    "        continue\n",
    "\n",
    "    # [Step B] 라벨 정보 파싱\n",
    "    classes_in_file = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                cls_id = int(parts[0])\n",
    "                classes_in_file.add(cls_id)\n",
    "    \n",
    "    if classes_in_file:\n",
    "        file_class_map[basename] = list(classes_in_file)\n",
    "        for c in classes_in_file:\n",
    "            class_file_map[c].append(basename)\n",
    "\n",
    "all_files = list(file_class_map.keys())\n",
    "print(f\"\\n[필터링 결과]\")\n",
    "print(f\"- 세로형(Vertical) 제외됨: {skipped_vertical}장\")\n",
    "print(f\"- 이미지 없음 제외됨: {skipped_no_image}장\")\n",
    "print(f\"- 최종 유효(Landscape) 데이터 개수: {len(all_files)}장\")\n",
    "\n",
    "\n",
    "# ================= 2. 공통 함수 정의 =================\n",
    "\n",
    "def copy_files(selected_basenames, save_folder_name):\n",
    "    \"\"\"선택된 파일들을 지정된 폴더로 복사 (이미지만)\"\"\"\n",
    "    save_path = os.path.join(OUTPUT_ROOT, save_folder_name)\n",
    "    if os.path.exists(save_path):\n",
    "        shutil.rmtree(save_path)\n",
    "    os.makedirs(save_path, exist_ok=True)\n",
    "    \n",
    "    count = 0\n",
    "    for basename in selected_basenames:\n",
    "        for ext in ['.jpg', '.jpeg', '.png']:\n",
    "            src_img = os.path.join(IMAGE_DIR, basename + ext)\n",
    "            if os.path.exists(src_img):\n",
    "                shutil.copy(src_img, os.path.join(save_path, basename + ext))\n",
    "                count += 1\n",
    "                break\n",
    "    print(f\"-> 저장 완료: {save_path} ({count}장)\")\n",
    "\n",
    "def print_class_stats(selected_basenames, case_name):\n",
    "    \"\"\"선택된 파일들에 포함된 클래스 개수 통계 출력\"\"\"\n",
    "    stats = Counter()\n",
    "    for basename in selected_basenames:\n",
    "        # 해당 파일에 들어있는 클래스들을 모두 카운트\n",
    "        for cls_id in file_class_map[basename]:\n",
    "            stats[cls_id] += 1\n",
    "            \n",
    "    print(f\"\\n📊 [{case_name}] 클래스 분포 통계 (이미지 {len(selected_basenames)}장)\")\n",
    "    print(\"-\" * 40)\n",
    "    print(f\"{'Class ID':<10} | {'Count (Objects)':<15}\")\n",
    "    print(\"-\" * 40)\n",
    "    \n",
    "    sorted_stats = sorted(stats.items())\n",
    "    for cls_id, count in sorted_stats:\n",
    "        mark = \" (*Target)\" if cls_id in TARGET_CLASSES else \" (Warning: Non-Target)\"\n",
    "        print(f\"{cls_id:<10} | {count:<15}{mark}\")\n",
    "    print(\"-\" * 40)\n",
    "    print(f\"Total Objects: {sum(stats.values())}\\n\")\n",
    "\n",
    "\n",
    "# ================= 3. 실험별 데이터 추출 =================\n",
    "\n",
    "# --- Case 1: Uniform ---\n",
    "print(f\"\\n{'='*20} Case 1: Uniform {'='*20}\")\n",
    "selected_uniform = set()\n",
    "current_counts = Counter()\n",
    "temp_pool = all_files[:]\n",
    "random.shuffle(temp_pool)\n",
    "\n",
    "while len(selected_uniform) < TOTAL_COUNT and temp_pool:\n",
    "    counts_list = [(c, current_counts[c]) for c in range(16)]\n",
    "    counts_list.sort(key=lambda x: x[1])\n",
    "    target_cls = counts_list[0][0]\n",
    "    \n",
    "    found_img = None\n",
    "    candidates = [f for f in class_file_map[target_cls] if f not in selected_uniform]\n",
    "    if candidates:\n",
    "        found_img = random.choice(candidates)\n",
    "    \n",
    "    if found_img is None:\n",
    "        remaining = [f for f in temp_pool if f not in selected_uniform]\n",
    "        if remaining:\n",
    "            found_img = remaining[0]\n",
    "        else:\n",
    "            break\n",
    "    \n",
    "    if found_img:\n",
    "        selected_uniform.add(found_img)\n",
    "        for c in file_class_map[found_img]:\n",
    "            current_counts[c] += 1\n",
    "\n",
    "copy_files(selected_uniform, \"1_uniform_300\")\n",
    "print_class_stats(selected_uniform, \"Uniform\")\n",
    "\n",
    "\n",
    "# --- Case 2: Target Bias ---\n",
    "print(f\"\\n{'='*20} Case 2: Target Bias {'='*20}\")\n",
    "bias_target_count = 250\n",
    "# 타겟 클래스 교집합이 있는 것 (기존 로직 유지)\n",
    "target_files = [f for f in all_files if not set(file_class_map[f]).isdisjoint(TARGET_CLASSES)]\n",
    "other_files = [f for f in all_files if f not in target_files]\n",
    "\n",
    "selected_bias = []\n",
    "if len(target_files) >= bias_target_count:\n",
    "    selected_bias.extend(random.sample(target_files, bias_target_count))\n",
    "else:\n",
    "    selected_bias.extend(target_files)\n",
    "\n",
    "remaining_needed = TOTAL_COUNT - len(selected_bias)\n",
    "if len(other_files) >= remaining_needed:\n",
    "    selected_bias.extend(random.sample(other_files, remaining_needed))\n",
    "else:\n",
    "    selected_bias.extend(other_files)\n",
    "\n",
    "copy_files(selected_bias, \"2_target_bias_300\")\n",
    "print_class_stats(selected_bias, \"Target Bias\")\n",
    "\n",
    "\n",
    "# --- Case 3: Target Only (Pure) ---\n",
    "# 수정됨: 이미지 내의 모든 객체가 TARGET_CLASSES에 포함되어야 함\n",
    "print(f\"\\n{'='*20} Case 3: Pure Target Only {'='*20}\")\n",
    "\n",
    "pure_target_files = []\n",
    "for fname in all_files:\n",
    "    classes_in_img = set(file_class_map[fname])\n",
    "    # [핵심 변경] 부분집합(subset)이어야 함. 즉, 타겟 클래스 외에는 아무것도 없어야 함.\n",
    "    if classes_in_img.issubset(TARGET_CLASSES):\n",
    "        pure_target_files.append(fname)\n",
    "\n",
    "print(f\"조건을 만족하는(Target Only) 이미지는 총 {len(pure_target_files)}장 입니다.\")\n",
    "\n",
    "selected_only = []\n",
    "if len(pure_target_files) >= TOTAL_COUNT:\n",
    "    selected_only = random.sample(pure_target_files, TOTAL_COUNT)\n",
    "else:\n",
    "    selected_only = pure_target_files # 300장이 안 되면 있는 대로 다 가져옴\n",
    "\n",
    "copy_files(selected_only, \"3_target_only_pure\")\n",
    "print_class_stats(selected_only, \"Pure Target Only\")\n",
    "\n",
    "print(\"모든 작업 완료!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a6e99107",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이미지 리사이즈 시작: 목표 해상도 (640x384)\n",
      "\n",
      "📂 Processing [1_uniform_300] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:02<00:00, 146.90it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "📂 Processing [2_target_bias_300] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:01<00:00, 153.25it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "📂 Processing [3_target_only_300] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:01<00:00, 152.73it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "📂 Processing [3_target_only_pure] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:01<00:00, 150.25it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 모든 이미지 리사이즈 완료!\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= 설정값 =================\n",
    "BASE_DIR = \"/home/cuuva/다운로드/quantization_experiments\"\n",
    "TARGET_FOLDERS = [\n",
    "    \"1_uniform_300\", \n",
    "    \"2_target_bias_300\", \n",
    "    \"3_target_only_300\", \n",
    "    \"3_target_only_pure\"\n",
    "]\n",
    "TARGET_WIDTH = 640\n",
    "TARGET_HEIGHT = 384\n",
    "\n",
    "# ================= 리사이즈 실행 =================\n",
    "\n",
    "print(f\"이미지 리사이즈 시작: 목표 해상도 ({TARGET_WIDTH}x{TARGET_HEIGHT})\")\n",
    "\n",
    "for folder in TARGET_FOLDERS:\n",
    "    folder_path = os.path.join(BASE_DIR, folder)\n",
    "    \n",
    "    # 폴더 존재 여부 확인\n",
    "    if not os.path.exists(folder_path):\n",
    "        print(f\"Skipping: {folder} (폴더가 없습니다)\")\n",
    "        continue\n",
    "        \n",
    "    # 이미지 파일 리스트업\n",
    "    image_files = []\n",
    "    for ext in ['*.jpg', '*.jpeg', '*.png', '*.bmp']:\n",
    "        image_files.extend(glob(os.path.join(folder_path, ext)))\n",
    "    \n",
    "    print(f\"\\n📂 Processing [{folder}] - {len(image_files)} images\")\n",
    "    \n",
    "    # 리사이즈 진행\n",
    "    for img_path in tqdm(image_files):\n",
    "        try:\n",
    "            with Image.open(img_path) as img:\n",
    "                # 이미지가 이미 목표 사이즈라면 건너뜀 (불필요한 연산 방지)\n",
    "                if img.size == (TARGET_WIDTH, TARGET_HEIGHT):\n",
    "                    continue\n",
    "                \n",
    "                # 리사이즈 (LANCZOS 필터 사용: 고품질)\n",
    "                resized_img = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.Resampling.LANCZOS)\n",
    "                \n",
    "                # 덮어쓰기 저장\n",
    "                resized_img.save(img_path)\n",
    "                \n",
    "        except Exception as e:\n",
    "            print(f\"Error processing {os.path.basename(img_path)}: {e}\")\n",
    "\n",
    "print(\"\\n✨ 모든 이미지 리사이즈 완료!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84464896",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "1stagedetect",
   "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.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
