{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":8089,"databundleVersionId":44321,"sourceType":"competition"},{"sourceId":13794204,"sourceType":"datasetVersion","datasetId":8782152}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install stardist\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-21T04:22:03.006929Z","iopub.execute_input":"2025-11-21T04:22:03.00721Z","iopub.status.idle":"2025-11-21T04:22:06.828061Z","shell.execute_reply.started":"2025-11-21T04:22:03.007189Z","shell.execute_reply":"2025-11-21T04:22:06.827314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nfrom skimage.io import imread\nfrom skimage.color import rgb2gray\nfrom sklearn.model_selection import train_test_split\nfrom stardist.models import Config2D, StarDist2D\nfrom csbdeep.utils import normalize\nimport albumentations as A\nimport zipfile\nimport shutil\n\n# ==========================================\n# 1. CÀI ĐẶT & GIẢI NÉN DỮ LIỆU (DSB 2018)\n# ==========================================\n# Thay đường dẫn này bằng dataset của bạn nếu khác\nDATASET_PATH = '/kaggle/input/data-science-bowl-2018/stage1_train.zip'\nEXTRACT_PATH = 'stage1_train/'\n\nif not os.path.exists(EXTRACT_PATH):\n    print(\"📦 Đang giải nén dữ liệu...\")\n    if os.path.exists(DATASET_PATH):\n        with zipfile.ZipFile(DATASET_PATH, 'r') as zip_ref:\n            zip_ref.extractall(EXTRACT_PATH)\n    else:\n        print(\"⚠️ CẢNH BÁO: Không thấy file zip DSB2018. Hãy Add Data trước!\")\n\n# ==========================================\n# 2. LOAD DỮ LIỆU & XỬ LÝ CHO BONG BÓNG\n# ==========================================\ndef load_training_data(base_path):\n    train_ids = next(os.walk(base_path))[1]\n    X = []\n    Y = []\n    \n    print(f\"🔄 Đang load {len(train_ids)} ảnh...\")\n    for n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):\n        path = base_path + id_\n        \n        # --- Đọc ảnh ---\n        img = imread(path + '/images/' + id_ + '.png')\n        \n        # 1. Bỏ kênh Alpha\n        if len(img.shape) == 3 and img.shape[2] == 4: \n            img = img[:,:,:3]\n        \n        # 2. Chuyển Grayscale (Quan trọng)\n        if len(img.shape) == 3: \n            img = rgb2gray(img)\n        \n        # 3. [QUAN TRỌNG] Đảo ngược màu nếu nền trắng (Brightfield)\n        # Để bong bóng trở thành vật thể sáng trên nền tối -> StarDist học tốt hơn\n        if np.mean(img) > 0.5:\n            img = 1.0 - img\n            \n        # --- Đọc Mask (Gộp các mask rời thành 1) ---\n        mask_path = path + '/masks/'\n        mask_files = next(os.walk(mask_path))[2]\n        mask_accum = np.zeros((img.shape[0], img.shape[1]), dtype=np.int32)\n        \n        for i, f in enumerate(mask_files):\n            m = imread(mask_path + f)\n            m = m > 0\n            # Gán ID riêng biệt (1, 2, 3...) -> Giúp model hiểu chồng lấn\n            mask_accum = np.maximum(mask_accum, m * (i+1))\n            \n        X.append(img)\n        Y.append(mask_accum)\n        \n    return X, Y\n\nX_raw, Y_raw = load_training_data(EXTRACT_PATH)\n\n# Chuẩn hóa (Normalize)\nprint(\"🔄 Đang chuẩn hóa dữ liệu...\")\nX_norm = [normalize(x, 1, 99.8, axis=(0,1)) for x in X_raw]\n\n# Chia tập Train/Val\nX_trn, X_val, Y_trn, Y_val = train_test_split(X_norm, Y_raw, test_size=0.15, random_state=42)\nprint(f\"✅ Dữ liệu sẵn sàng: Train={len(X_trn)}, Val={len(X_val)}\")\n\n# ==========================================\n# 3. AUGMENTATION (GIẢ LẬP CHỒNG LẤN & MÉO)\n# ==========================================\ndef get_strong_augmentation():\n    return A.Compose([\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        \n        # ElasticTransform: Giả lập bong bóng bị méo khi chèn ép nhau\n        A.ElasticTransform(alpha=1, sigma=50, alpha_affine=None, p=0.4),\n        \n        # Thay đổi độ sáng/tương phản để model quen với bong bóng mờ/rõ\n        A.RandomBrightnessContrast(p=0.5),\n        \n        # Blur nhẹ để giả lập bong bóng out-focus\n        A.GaussianBlur(blur_limit=(3, 5), p=0.2),\n    ])\n\naug_pipeline = get_strong_augmentation()\n\ndef augmenter(x, y):\n    x = x.astype(np.float32)\n    y = y.astype(np.int32)\n    augmented = aug_pipeline(image=x, mask=y)\n    return augmented['image'], augmented['mask']\n\n# ==========================================\n# 4. CẤU HÌNH STARDIST (TỐI ƯU CHO CHỒNG LẤN)\n# ==========================================\n# Xóa model cũ nếu có để train mới hoàn toàn\nif os.path.exists('models/stardist_overlap_v1'):\n    shutil.rmtree('models/stardist_overlap_v1')\n\nconf = Config2D(\n    # --- QUAN TRỌNG: SỐ TIA ---\n    # Tăng lên 64 (mặc định 32) để vẽ đường viền chi tiết hơn cho các vật thể dính nhau\n    n_rays = 64,\n    \n    # Grid (2,2) giúp train nhanh. Nếu muốn chính xác tuyệt đối có thể để (1,1) nhưng rất chậm\n    grid = (1,1),\n    \n    # Cấu trúc mạng\n    unet_n_filter_base = 48, # Tăng số filter để model thông minh hơn\n    unet_n_depth = 4,        # Mạng sâu hơn\n    \n    # Tắt OpenCL GPU (tránh lỗi gputools trên Kaggle), TF vẫn dùng GPU train bình thường\n    use_gpu = False, \n    \n    n_channel_in = 1,\n    train_patch_size = (256, 256),\n    train_batch_size = 8,\n    train_learning_rate = 0.0003,\n)\n\nmodel = StarDist2D(conf, name='stardist_overlap_v1', basedir='models')\n\n# ==========================================\n# 5. HUẤN LUYỆN (TRAINING)\n# ==========================================\nprint(\"🚀 Bắt đầu Train model nhận diện chồng lấn...\")\n\nhistory = model.train(\n    X_trn, Y_trn, \n    validation_data=(X_val, Y_val), \n    augmenter=augmenter,\n    epochs=100,              # Tăng epoch vì bài toán khó hơn\n    steps_per_epoch=100\n)\n\n# ==========================================\n# 6. TỐI ƯU HÓA & ĐÓNG GÓI\n# ==========================================\nprint(\"⚖️ Đang tối ưu hóa ngưỡng (Thresholds)...\")\n# Bước này giúp model tìm ra ngưỡng tốt nhất để tách vật thể\nmodel.optimize_thresholds(X_val, Y_val)\n\n# Vẽ biểu đồ Loss\nplt.figure(figsize=(8,5))\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Val Loss')\nplt.title('Training Loss')\nplt.legend()\nplt.show()\n\n# Nén model để tải về\nprint(\"📦 Đang nén model...\")\nshutil.make_archive('best_bubble_model', 'zip', 'models/stardist_overlap_v1')\nprint(\"✅ HOÀN TẤT! Hãy tải file 'best_bubble_model.zip' trong phần Output.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T04:22:09.460994Z","iopub.execute_input":"2025-11-21T04:22:09.46165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}