{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"accelerator":"GPU","colab":{"gpuType":"T4","provenance":[]}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Library Import","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\nfrom joblib import Parallel, delayed\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split","metadata":{"id":"0UGsKWxLm2V7","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:11.209532Z","iopub.execute_input":"2026-09-21T05:47:11.209815Z","iopub.status.idle":"2026-09-21T05:47:15.213688Z","shell.execute_reply.started":"2026-09-21T05:47:11.209784Z","shell.execute_reply":"2026-09-21T05:47:15.212775Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Import & Read","metadata":{}},{"cell_type":"code","source":"for dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames[:3]: \n        print(os.path.join(dirname, filename))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:15.214683Z","iopub.execute_input":"2026-09-21T05:47:15.215081Z","iopub.status.idle":"2026-09-21T05:47:20.673637Z","shell.execute_reply.started":"2026-09-21T05:47:15.215051Z","shell.execute_reply":"2026-09-21T05:47:20.672594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\nCSV_PATH = os.path.join(DATA_DIR, \"train.csv\")\nIMAGE_DIR = os.path.join(DATA_DIR, \"train_images\")","metadata":{"id":"eUYOLdvlhq72","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:20.675884Z","iopub.execute_input":"2026-09-21T05:47:20.676256Z","iopub.status.idle":"2026-09-21T05:47:20.681772Z","shell.execute_reply.started":"2026-09-21T05:47:20.676227Z","shell.execute_reply":"2026-09-21T05:47:20.680792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(CSV_PATH)\n\nlabel_names = {\n    0: \"No DR (Normal)\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"Proliferative DR\"\n}\nCLASS_NAMES = [label_names[i] for i in range(5)]\ndf['label_name'] = df['diagnosis'].map(label_names)","metadata":{"id":"gJfqj9P3nCon","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:20.682938Z","iopub.execute_input":"2026-09-21T05:47:20.683916Z","iopub.status.idle":"2026-09-21T05:47:20.730039Z","shell.execute_reply.started":"2026-09-21T05:47:20.683885Z","shell.execute_reply":"2026-09-21T05:47:20.72909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Total seluruh data: {len(df)} gambar\\n\")","metadata":{"id":"Ufk089D_nIYk","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:20.731126Z","iopub.execute_input":"2026-09-21T05:47:20.73147Z","iopub.status.idle":"2026-09-21T05:47:20.73687Z","shell.execute_reply.started":"2026-09-21T05:47:20.731433Z","shell.execute_reply":"2026-09-21T05:47:20.735959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df['label_name'].value_counts())","metadata":{"id":"SrFXM-_qnJ3G","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:20.737878Z","iopub.execute_input":"2026-09-21T05:47:20.738177Z","iopub.status.idle":"2026-09-21T05:47:20.763255Z","shell.execute_reply.started":"2026-09-21T05:47:20.738144Z","shell.execute_reply":"2026-09-21T05:47:20.762194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Pastikan Anda sudah menginstal library-nya: pip install imbalanced-learn\nfrom imblearn.under_sampling import RandomUnderSampler\nimport pandas as pd\n\n# 1. Hitung jumlah sampel per kelas saat ini\nclass_counts = df['diagnosis'].value_counts()\n\n# 2. Tentukan target jumlah data baru untuk kelas 0 setelah dipotong\n# Contoh: Kita set kuota kelas 0 agar sama dengan jumlah sampel kelas 2\ntarget_class_0_size = class_counts[2] \n\n# 3. Buat dictionary sampling_strategy untuk kelas yang ingin di-undersample saja\n# Kelas 1, 2, 3, 4 akan mempertahankan jumlah aslinya secara otomatis\nsampling_strategy = {\n    0: target_class_0_size,\n    1: class_counts[1],\n    2: class_counts[2],\n    3: class_counts[3],\n    4: class_counts[4]\n}\n\n# 4. Inisialisasi sampler dan terapkan pada data\nrus = RandomUnderSampler(sampling_strategy=sampling_strategy, random_state=42)\n\n# Pisahkan fitur indeks/path gambar dengan target label untuk kebutuhan format imblearn\nX = df.drop(columns=['diagnosis'])\ny = df['diagnosis']\n\nX_resampled, y_resampled = rus.fit_resample(X, y)\n\n# 5. Satukan kembali menjadi DataFrame baru yang seimbang\ndf_balanced = pd.DataFrame(X_resampled)\ndf_balanced['diagnosis'] = y_resampled\ndf_balanced['label_name'] = df_balanced['diagnosis'].map(label_names)\n\ndf = df_balanced\n\n# Verifikasi hasil akhir\nprint(df['label_name'].value_counts())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:20.764407Z","iopub.execute_input":"2026-09-21T05:47:20.764737Z","iopub.status.idle":"2026-09-21T05:47:22.349482Z","shell.execute_reply.started":"2026-09-21T05:47:20.76471Z","shell.execute_reply":"2026-09-21T05:47:22.348543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport matplotlib.pyplot as plt\n\n# 1. Tentukan direktori output Kaggle\nOUTPUT_DIR = '/kaggle/working/'\nos.makedirs(OUTPUT_DIR, exist_ok=True)\n\n# Parameter ukuran resize\nIMAGE_SIZE = (224, 224)\n\n# 2. Ambil 1 sampel gambar (misal: sampel pertama dari dataframe)\nsample = df.iloc[3]\nid_code = sample['id_code']\n\nimg_name = f\"{id_code}.png\"\nimg_path = os.path.join(IMAGE_DIR, img_name)\n\n# 3. Baca gambar asli & konversi BGR ke RGB\nimg_original_bgr = cv2.imread(img_path)\nimg_original_rgb = cv2.cvtColor(img_original_bgr, cv2.COLOR_BGR2RGB)\n\n# 4. Resize gambar ke 224x224\nimg_resized_rgb = cv2.resize(img_original_rgb, IMAGE_SIZE, interpolation=cv2.INTER_AREA)\n\n# --- OPSI B: Menyimpan plot perbandingan bersandingan (1 gambar berisi keduanya) ---\nfig, axes = plt.subplots(1, 2, figsize=(10, 5))\nfig.suptitle(f\"Perbandingan Ukuran Gambar - ID: {id_code} (Label {sample['diagnosis']}: {sample['label_name']})\", \n             fontsize=12, fontweight='bold')\n\n# Subplot 1: Gambar Asli\naxes[0].imshow(img_original_rgb)\naxes[0].set_title(f\"Gambar Asli\\nDimensi: {img_original_rgb.shape[1]} x {img_original_rgb.shape[0]} px\")\naxes[0].axis('off')\n\n# Subplot 2: Gambar Resize\naxes[1].imshow(img_resized_rgb)\naxes[1].set_title(f\"Hasil Resize\\nDimensi: {IMAGE_SIZE[0]} x {IMAGE_SIZE[1]} px\")\naxes[1].axis('off')\n\nplt.tight_layout()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:22.350713Z","iopub.execute_input":"2026-09-21T05:47:22.351244Z","iopub.status.idle":"2026-09-21T05:47:23.077548Z","shell.execute_reply.started":"2026-09-21T05:47:22.351212Z","shell.execute_reply":"2026-09-21T05:47:23.076398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 5, figsize=(20, 5))\nfig.suptitle(\"Contoh Gambar untuk Setiap Tingkat Keparahan DR\", fontsize=16, fontweight='bold')\n\nfor i in range(5):\n    sample = df[df['diagnosis'] == i].iloc[0]\n\n    img_name = f\"{sample['id_code']}.png\"\n    img_path = os.path.join(IMAGE_DIR, img_name)\n\n    img = cv2.imread(img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    axes[i].imshow(img)\n    axes[i].set_title(f\"Label {i}: {sample['label_name']}\\nID: {sample['id_code']}\", fontsize=10)\n    axes[i].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"id":"vTKEw3gunLZC","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:23.081176Z","iopub.execute_input":"2026-09-21T05:47:23.081639Z","iopub.status.idle":"2026-09-21T05:47:26.000905Z","shell.execute_reply.started":"2026-09-21T05:47:23.081607Z","shell.execute_reply":"2026-09-21T05:47:25.99946Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Split","metadata":{}},{"cell_type":"code","source":"train_df, test_val_df = train_test_split(\n    df,\n    test_size=0.3,\n    random_state=42,\n    stratify=df['diagnosis']\n)\n\nval_df, test_df = train_test_split(\n    test_val_df,\n    test_size=0.5,\n    random_state=42,\n    stratify=test_val_df['diagnosis']\n)","metadata":{"id":"NPl6bIfwsSEj","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.002447Z","iopub.execute_input":"2026-09-21T05:47:26.002844Z","iopub.status.idle":"2026-09-21T05:47:26.016891Z","shell.execute_reply.started":"2026-09-21T05:47:26.002802Z","shell.execute_reply":"2026-09-21T05:47:26.016045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Jumlah data Training   : {len(train_df)} gambar ({len(train_df)/len(df)*100:.1f}%)\")\nprint(f\"Jumlah data Validation : {len(val_df)} gambar ({len(val_df)/len(df)*100:.1f}%)\")\nprint(f\"Jumlah data Testing    : {len(test_df)} gambar ({len(test_df)/len(df)*100:.1f}%)\")\nprint(f\"Total data             : {len(train_df) + len(val_df) + len(test_df)} gambar\")","metadata":{"id":"WkdEEyKKsVaF","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.018068Z","iopub.execute_input":"2026-09-21T05:47:26.018576Z","iopub.status.idle":"2026-09-21T05:47:26.030851Z","shell.execute_reply.started":"2026-09-21T05:47:26.018536Z","shell.execute_reply":"2026-09-21T05:47:26.029961Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Filter Data Val & Test","metadata":{}},{"cell_type":"code","source":"IMAGE_SIZE = 224\nINTERMEDIATE_SIZE = 896\n# IMAGE_SIZE = 384\n# INTERMEDIATE_SIZE = 768","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.032053Z","iopub.execute_input":"2026-09-21T05:47:26.032517Z","iopub.status.idle":"2026-09-21T05:47:26.048382Z","shell.execute_reply.started":"2026-09-21T05:47:26.032476Z","shell.execute_reply":"2026-09-21T05:47:26.047402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cv2.setNumThreads(0)\ncv2.ocl.setUseOpenCL(False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.049701Z","iopub.execute_input":"2026-09-21T05:47:26.050087Z","iopub.status.idle":"2026-09-21T05:47:26.064168Z","shell.execute_reply.started":"2026-09-21T05:47:26.050048Z","shell.execute_reply":"2026-09-21T05:47:26.063397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def crop_and_letterbox(img, tol=10):\n    # 1. AUTO-CROP BERBASIS KONTUR LINGKARAN TERBESAR\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    \n    # Berikan sedikit blur untuk menghaluskan noise bintik hitam kecil\n    blurred = cv2.GaussianBlur(gray, (5, 5), 0)\n    _, thresh = cv2.threshold(blurred, tol, 255, cv2.THRESH_BINARY)\n    \n    # Temukan kontur lingkaran luar retina\n    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    \n    if contours:\n        # Ambil kontur dengan area terbesar (pasti bulatan retina)\n        largest_contour = max(contours, key=cv2.contourArea)\n        x, y, w, h = cv2.boundingRect(largest_contour)\n        \n        # Potong gambar berdasarkan koordinat bounding box kontur tersebut\n        img = img[y:y+h, x:x+w]\n    \n    # 2. LETTERBOX: Menambahkan padding hitam murni agar aspek rasio menjadi 1:1 (Persegi murni)\n    h, w = img.shape[:2]\n    max_side = max(h, w)\n    \n    pad_y = (max_side - h) // 2\n    pad_x = (max_side - w) // 2\n    pad_y_back = max_side - h - pad_y\n    pad_x_back = max_side - w - pad_x\n    \n    img_square = cv2.copyMakeBorder(\n        img, pad_y, pad_y_back, pad_x, pad_x_back,\n        borderType=cv2.BORDER_CONSTANT, value=(0, 0, 0)\n    )\n    \n    return img_square\n\n    \ndef resize_image(img, image_size):\n    return cv2.resize(img, (image_size, image_size), interpolation=cv2.INTER_AREA)\n\ndef image_filter(img, image_size):\n    img_resized = cv2.resize(img, (image_size, image_size))\n    \n    blurred = cv2.GaussianBlur(img_resized, (0, 0), sigmaX=10)\n    \n    img_filtered = cv2.addWeighted(img_resized, 4, blurred, -4, 128)\n    \n    return img_filtered\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.065146Z","iopub.execute_input":"2026-09-21T05:47:26.065437Z","iopub.status.idle":"2026-09-21T05:47:26.076285Z","shell.execute_reply.started":"2026-09-21T05:47:26.065411Z","shell.execute_reply":"2026-09-21T05:47:26.075274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_single_filter(id_code, source_dir, output_dir, image_size, is_augmented=False):\n    try:\n        src_path = os.path.join(source_dir, f\"{id_code}.png\")\n        dst_path = os.path.join(output_dir, f\"{id_code}.png\")\n\n        if os.path.exists(dst_path):\n            return True\n\n        img = cv2.imread(src_path)\n        if img is None:\n            return False\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        if is_augmented:\n            img_ben = image_filter(img, image_size)\n        else:\n            # === PERBAIKAN 1: Resize Awal dengan Mempertahankan Aspek Rasio Asli ===\n            h, w = img.shape[:2]\n            scale = INTERMEDIATE_SIZE / max(h, w)\n            new_h, new_w = int(h * scale), int(w * scale)\n            img_fast = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)\n            \n            # Sekarang gambar masuk ke fungsi crop dalam kondisi proporsional (tidak penyet)\n            img_square = crop_and_letterbox(img_fast, tol=10)\n            img_resized = resize_image(img_square, image_size)\n            img_ben = image_filter(img_resized, image_size) \n\n        cv2.imwrite(dst_path, cv2.cvtColor(img_ben, cv2.COLOR_RGB2BGR))\n        return True\n    except Exception:\n        return False\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.077376Z","iopub.execute_input":"2026-09-21T05:47:26.077656Z","iopub.status.idle":"2026-09-21T05:47:26.093223Z","shell.execute_reply.started":"2026-09-21T05:47:26.077629Z","shell.execute_reply":"2026-09-21T05:47:26.092155Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_filter(df, source_dir, output_dir, image_size, is_augmented=False):\n    os.makedirs(output_dir, exist_ok=True)\n    n_jobs = os.cpu_count()  # Membaca jumlah core maksimal yang tersedia di Kaggle\n\n    # OPTIMALISASI: Menggunakan Joblib Parallel untuk memproses gambar secara multi-core\n    Parallel(n_jobs=n_jobs)(\n        delayed(process_single_filter)(id_code, source_dir, output_dir, image_size, is_augmented)\n        for id_code in tqdm(df['id_code'], desc=f\"Menerapkan Resize & Filter ({len(df)} data)\")\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.094462Z","iopub.execute_input":"2026-09-21T05:47:26.095087Z","iopub.status.idle":"2026-09-21T05:47:26.113728Z","shell.execute_reply.started":"2026-09-21T05:47:26.095047Z","shell.execute_reply":"2026-09-21T05:47:26.112902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"VAL_TEST_DIR = \"aptos_data/val_test\"\nos.makedirs(VAL_TEST_DIR, exist_ok=True)\nBALANCED_DIR = \"aptos_data/train_balanced\"\nos.makedirs(BALANCED_DIR, exist_ok=True)\nAUGMENTED_DIR = \"aptos_data/augmented_raw\"\nos.makedirs(AUGMENTED_DIR, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.114808Z","iopub.execute_input":"2026-09-21T05:47:26.115107Z","iopub.status.idle":"2026-09-21T05:47:26.128652Z","shell.execute_reply.started":"2026-09-21T05:47:26.11507Z","shell.execute_reply":"2026-09-21T05:47:26.127729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"apply_filter(val_df, IMAGE_DIR, VAL_TEST_DIR, IMAGE_SIZE)\napply_filter(test_df, IMAGE_DIR, VAL_TEST_DIR, IMAGE_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:47:26.129821Z","iopub.execute_input":"2026-09-21T05:47:26.130187Z","iopub.status.idle":"2026-09-21T05:48:38.894566Z","shell.execute_reply.started":"2026-09-21T05:47:26.130151Z","shell.execute_reply":"2026-09-21T05:48:38.893532Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Augmentasi Data Train","metadata":{}},{"cell_type":"code","source":"AUG_CONFIG = {\n    \"prob_flip_horizontal\": 0.5,\n    \"prob_flip_vertical\":   0.3,\n    \"prob_rotation\":        0.7,\n    \"range_rotation\":       (-180, 180),\n    \"prob_scaling\":         0.6,\n    \"range_scaling\":        (0.8, 1.25),\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:48:38.897161Z","iopub.execute_input":"2026-09-21T05:48:38.897585Z","iopub.status.idle":"2026-09-21T05:48:38.902803Z","shell.execute_reply.started":"2026-09-21T05:48:38.89754Z","shell.execute_reply":"2026-09-21T05:48:38.902051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment_image(image, config=AUG_CONFIG):\n    # === PERBAIKAN DISTORSI: Resizing proporsional sebelum augmentasi ===\n    h, w = image.shape[:2]\n    scale_factor = INTERMEDIATE_SIZE / max(h, w)\n    new_h, new_w = int(h * scale_factor), int(w * scale_factor)\n    img_fast = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)\n    \n    img_square = crop_and_letterbox(img_fast, tol=10)\n    \n    max_side = int(img_square.shape[0])\n    center = (max_side // 2, max_side // 2)\n\n    # 1. Flip Horizontal\n    if np.random.rand() < config[\"prob_flip_horizontal\"]:\n        img_square = cv2.flip(img_square, 1)\n \n    # 2. Flip Vertikal\n    if np.random.rand() < config[\"prob_flip_vertical\"]:\n        img_square = cv2.flip(img_square, 0)\n        \n    # 3. Rotasi Aman\n    if np.random.rand() < config[\"prob_rotation\"]:\n        angle = np.random.uniform(*config[\"range_rotation\"])\n        rot_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)\n        img_square = cv2.warpAffine(\n            img_square, \n            rot_matrix, \n            (max_side, max_side), \n            borderMode=cv2.BORDER_CONSTANT, \n            borderValue=(0, 0, 0)\n        )\n        \n    # 4. Scaling Aman\n    if np.random.rand() < config[\"prob_scaling\"]:\n        scale = np.random.uniform(*config[\"range_scaling\"])\n        new_size = int(max_side * scale)\n        img_scaled = cv2.resize(img_square, (new_size, new_size), interpolation=cv2.INTER_LINEAR)\n \n        if scale > 1.0:\n            start_pos = (new_size - max_side) // 2\n            img_square = img_scaled[start_pos:start_pos + max_side, start_pos:start_pos + max_side]\n        else:\n            pad_val = (max_side - new_size) // 2\n            pad_val_back = max_side - new_size - pad_val\n            img_square = cv2.copyMakeBorder(\n                img_scaled,\n                pad_val, pad_val_back,\n                pad_val, pad_val_back,\n                borderType=cv2.BORDER_CONSTANT,\n                value=(0, 0, 0)\n            )\n            \n    img_square = cv2.resize(img_square, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_AREA)        \n    return img_square\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:48:38.903871Z","iopub.execute_input":"2026-09-21T05:48:38.904161Z","iopub.status.idle":"2026-09-21T05:48:38.920806Z","shell.execute_reply.started":"2026-09-21T05:48:38.904135Z","shell.execute_reply":"2026-09-21T05:48:38.919779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def _process_single_aug_task(task_args):\n    src_row, original_dir, output_dir, aug_counter, config = task_args\n    try:\n        src_path = os.path.join(original_dir, f\"{src_row['id_code']}.png\")\n        img = cv2.imread(src_path)\n        if img is None:\n            return None\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        img_aug = augment_image(img, config)\n\n        new_id_code = f\"{src_row['id_code']}_aug{aug_counter}\"\n        dst_path = os.path.join(output_dir, f\"{new_id_code}.png\")\n        cv2.imwrite(dst_path, cv2.cvtColor(img_aug, cv2.COLOR_RGB2BGR))\n\n        return {\"id_code\": new_id_code, \"diagnosis\": src_row['diagnosis'], \"is_augmented\": True}\n    except Exception:\n        return None\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:48:38.922Z","iopub.execute_input":"2026-09-21T05:48:38.922303Z","iopub.status.idle":"2026-09-21T05:48:38.939312Z","shell.execute_reply.started":"2026-09-21T05:48:38.922268Z","shell.execute_reply":"2026-09-21T05:48:38.938396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_augment(df, original_dir, output_dir, config=AUG_CONFIG, target_limit=500, seed=42):\n    \n    np.random.seed(seed)\n\n    class_counts_local = df['diagnosis'].value_counts()\n    \n    print(f\"Batas target jumlah sampel per kelas: {target_limit}\")\n    print(f\"Distribusi awal:\\n{class_counts_local.sort_index()}\\n\")\n\n    # Kumpulkan seluruh daftar antrean tugas (tasks) terlebih dahulu\n    tasks = []\n    for cls in sorted(df['diagnosis'].unique()):\n        cls_df = df[df['diagnosis'] == cls].reset_index(drop=True)\n        n_existing = len(cls_df)\n        \n        # Hitung kebutuhan augmentasi berdasarkan target_limit, bukan maksimal kelas mayoritas\n        n_needed = max(0, target_limit - n_existing)\n\n        print(f\"Kelas {cls}: {n_existing} data asli, perlu {n_needed} data augmentasi tambahan\")\n        \n        for aug_counter in range(n_needed):\n            src_row = cls_df.sample(1).iloc[0].to_dict()\n            tasks.append((src_row, original_dir, output_dir, aug_counter, config))\n\n    if len(tasks) == 0:\n        print(\"\\nTidak ada gambar yang perlu diaugmentasi karena semua kelas sudah memenuhi target.\")\n        return pd.DataFrame()\n\n    print(f\"\\nMemulai pembuatan {len(tasks)} gambar augmentasi secara paralel...\")\n    n_jobs = os.cpu_count()\n    \n    # Eksekusi antrean tugas secara paralel memanfaatkan seluruh core CPU\n    results = Parallel(n_jobs=n_jobs)(\n        delayed(_process_single_aug_task)(task) \n        for task in tqdm(tasks, desc=\"Paralelisasi Augmentasi\")\n    )\n\n    # Saring data yang sukses (tidak None)\n    augmented_rows = [r for r in results if r is not None]\n    return pd.DataFrame(augmented_rows)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:48:38.940819Z","iopub.execute_input":"2026-09-21T05:48:38.94114Z","iopub.status.idle":"2026-09-21T05:48:38.95636Z","shell.execute_reply.started":"2026-09-21T05:48:38.941104Z","shell.execute_reply":"2026-09-21T05:48:38.955404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"augmented_df = generate_augment(train_df, IMAGE_DIR, AUGMENTED_DIR, config=AUG_CONFIG, target_limit=500)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:48:38.957442Z","iopub.execute_input":"2026-09-21T05:48:38.957731Z","iopub.status.idle":"2026-09-21T05:50:01.46642Z","shell.execute_reply.started":"2026-09-21T05:48:38.957697Z","shell.execute_reply":"2026-09-21T05:50:01.46554Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Filter Data Train","metadata":{}},{"cell_type":"code","source":"print(\"=== Memproses Data Training Asli ===\")\napply_filter(train_df, IMAGE_DIR, BALANCED_DIR, IMAGE_SIZE)\n\nprint(\"\\n=== Memproses Data Training Augmentasi ===\")\napply_filter(augmented_df, AUGMENTED_DIR, BALANCED_DIR, IMAGE_SIZE, is_augmented=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:50:01.467518Z","iopub.execute_input":"2026-09-21T05:50:01.467817Z","iopub.status.idle":"2026-09-21T05:52:54.709287Z","shell.execute_reply.started":"2026-09-21T05:50:01.467783Z","shell.execute_reply":"2026-09-21T05:52:54.708606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nprint(\"\\n=== Membersihkan Direktori Augmentasi Raw ===\")\nif os.path.exists(AUGMENTED_DIR):\n    shutil.rmtree(AUGMENTED_DIR)\n    print(f\"Sukses menghapus {AUGMENTED_DIR}\")\n    \nbalanced_df = pd.concat([\n    train_df[['id_code', 'diagnosis']].assign(is_augmented=False),\n    augmented_df[['id_code', 'diagnosis', 'is_augmented']]\n], ignore_index=True)\n\nprint(\"\\n=== Distribusi setelah balancing ===\")\nprint(balanced_df['diagnosis'].value_counts().sort_index())\nprint(f\"Total data training: {len(balanced_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:52:54.710799Z","iopub.execute_input":"2026-09-21T05:52:54.711197Z","iopub.status.idle":"2026-09-21T05:52:54.752219Z","shell.execute_reply.started":"2026-09-21T05:52:54.711156Z","shell.execute_reply":"2026-09-21T05:52:54.751373Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Menentukan folder tempat penyimpanan file CSV\nCSV_OUTPUT_DIR = \"aptos_data/csv_metadata\"\nos.makedirs(CSV_OUTPUT_DIR, exist_ok=True)\n\n# Path file tujuan untuk masing-masing dataframe\nval_csv_path = os.path.join(CSV_OUTPUT_DIR, \"val_data.csv\")\ntest_csv_path = os.path.join(CSV_OUTPUT_DIR, \"test_data.csv\")\nbalanced_train_csv_path = os.path.join(CSV_OUTPUT_DIR, \"balanced_train_data.csv\")\n\n# Menyimpan Dataframe ke file CSV (tanpa menyertakan indeks baris)\nval_df.to_csv(val_csv_path, index=False)\ntest_df.to_csv(test_csv_path, index=False)\nbalanced_df.to_csv(balanced_train_csv_path, index=False)\n\nprint(\"=== Proses Penyimpanan CSV Selesai ===\")\nprint(f\"1. Data Validation disimpan ke : {val_csv_path} ({len(val_df)} baris)\")\nprint(f\"2. Data Testing disimpan ke    : {test_csv_path} ({len(test_df)} baris)\")\nprint(f\"3. Data Train Balanced ke      : {balanced_train_csv_path} ({len(balanced_df)} baris)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:52:54.753594Z","iopub.execute_input":"2026-09-21T05:52:54.753868Z","iopub.status.idle":"2026-09-21T05:52:54.775594Z","shell.execute_reply.started":"2026-09-21T05:52:54.753843Z","shell.execute_reply":"2026-09-21T05:52:54.774788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport random\nimport matplotlib.pyplot as plt\n\n# 1. Ambil seluruh file gambar yang ada di direktori output fisik\nall_files = [f for f in os.listdir(BALANCED_DIR) if f.endswith('.png')]\n\n# 2. Ambil tepat 5 sampel gambar secara acak menggunakan modul random\nrandom_samples = random.sample(all_files, 5)\n\n# 3. Tampilkan kelima gambar secara ringkas dalam 1 baris grid subplot\nfig, axes = plt.subplots(1, 5, figsize=(20, 4))\nfig.suptitle(\"Pengecekan Acak 5 Data dari Direktori Output\", fontsize=12, fontweight='bold')\n\nfor idx, filename in enumerate(random_samples):\n    # Baca gambar fisik BGR dari disk dan konversi ke RGB\n    img = cv2.imread(os.path.join(BALANCED_DIR, filename))\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    # Visualisasikan gambar beserta judul nama file aslinya\n    axes[idx].imshow(img)\n    axes[idx].set_title(filename, fontsize=8)\n    axes[idx].axis('off')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T05:52:54.778939Z","iopub.execute_input":"2026-09-21T05:52:54.779224Z","iopub.status.idle":"2026-09-21T05:52:55.56071Z","shell.execute_reply.started":"2026-09-21T05:52:54.779198Z","shell.execute_reply":"2026-09-21T05:52:55.559671Z"}},"outputs":[],"execution_count":null}]}