{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":18647,"databundleVersionId":1126921}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/competitions/prostate-cancer-grade-assessment\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:28:41.254564Z","iopub.execute_input":"2026-05-06T07:28:41.254915Z","iopub.status.idle":"2026-05-06T07:28:41.26589Z","shell.execute_reply.started":"2026-05-06T07:28:41.254874Z","shell.execute_reply":"2026-05-06T07:28:41.264856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\n\nBASE_PATH = \"/kaggle/input/competitions/prostate-cancer-grade-assessment\"\nTRAIN_CSV = os.path.join(BASE_PATH, \"train.csv\")\nTRAIN_IMAGES = os.path.join(BASE_PATH, \"train_images\")\n\nprint(os.listdir(BASE_PATH))\n\ndf = pd.read_csv(TRAIN_CSV)\n\nprint(df.head())\nprint(df.columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:35:18.816539Z","iopub.execute_input":"2026-05-06T07:35:18.817604Z","iopub.status.idle":"2026-05-06T07:35:18.843181Z","shell.execute_reply.started":"2026-05-06T07:35:18.817565Z","shell.execute_reply":"2026-05-06T07:35:18.842105Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[\"cancer_label\"] = (df[\"isup_grade\"] > 0).astype(int)\n\nprint(df[[\"isup_grade\", \"cancer_label\"]].head())\nprint(df[\"cancer_label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:35:20.967886Z","iopub.execute_input":"2026-05-06T07:35:20.968256Z","iopub.status.idle":"2026-05-06T07:35:20.979407Z","shell.execute_reply.started":"2026-05-06T07:35:20.968224Z","shell.execute_reply":"2026-05-06T07:35:20.978091Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SAMPLES_PER_CLASS = 250\nNUM_TILES = 8\nTILE_SIZE = 160\n\nbalanced_df = (\n    df.groupby(\"cancer_label\", group_keys=False)\n    .apply(lambda x: x.sample(min(len(x), SAMPLES_PER_CLASS), random_state=42))\n    .reset_index(drop=True)\n)\n\nprint(balanced_df[\"cancer_label\"].value_counts())\nprint(\"Total slides:\", len(balanced_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:35:32.507239Z","iopub.execute_input":"2026-05-06T07:35:32.507591Z","iopub.status.idle":"2026-05-06T07:35:32.525414Z","shell.execute_reply.started":"2026-05-06T07:35:32.50756Z","shell.execute_reply":"2026-05-06T07:35:32.524392Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q openslide-python\n!apt-get install -y openslide-tools > /dev/null\n\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport openslide\nimport tensorflow as tf\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:38:39.117905Z","iopub.execute_input":"2026-05-06T07:38:39.118625Z","iopub.status.idle":"2026-05-06T07:39:27.255419Z","shell.execute_reply.started":"2026-05-06T07:38:39.118589Z","shell.execute_reply":"2026-05-06T07:39:27.25438Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def tissue_score(tile):\n    tile_np = np.array(tile)\n    gray = cv2.cvtColor(tile_np, cv2.COLOR_RGB2GRAY)\n    return np.mean(gray < 220)\n\n\ndef extract_tiles(image_id, tile_size=160, num_tiles=8, level=1):\n    image_path = os.path.join(TRAIN_IMAGES, image_id + \".tiff\")\n    \n    if not os.path.exists(image_path):\n        return []\n    \n    slide = openslide.OpenSlide(image_path)\n    level = min(level, slide.level_count - 1)\n    w, h = slide.level_dimensions[level]\n    downsample = int(slide.level_downsamples[level])\n    \n    tiles = []\n    scores = []\n    \n    for y in range(0, h - tile_size, tile_size):\n        for x in range(0, w - tile_size, tile_size):\n            tile = slide.read_region(\n                (x * downsample, y * downsample),\n                level,\n                (tile_size, tile_size)\n            ).convert(\"RGB\")\n            \n            score = tissue_score(tile)\n            \n            if score > 0.05:\n                tiles.append(tile)\n                scores.append(score)\n    \n    slide.close()\n    \n    if len(tiles) == 0:\n        return []\n    \n    top_indices = np.argsort(scores)[-num_tiles:]\n    return [tiles[i] for i in top_indices]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:40:27.945962Z","iopub.execute_input":"2026-05-06T07:40:27.94743Z","iopub.status.idle":"2026-05-06T07:40:27.958059Z","shell.execute_reply.started":"2026-05-06T07:40:27.947383Z","shell.execute_reply":"2026-05-06T07:40:27.956668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_id = balanced_df.iloc[0][\"image_id\"]\n\ntiles = extract_tiles(sample_id, tile_size=160, num_tiles=8, level=1)\n\nprint(\"Number of tiles:\", len(tiles))\n\nplt.figure(figsize=(12, 6))\nfor i, tile in enumerate(tiles):\n    plt.subplot(2, 4, i + 1)\n    plt.imshow(tile)\n    plt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:40:47.074133Z","iopub.execute_input":"2026-05-06T07:40:47.074495Z","iopub.status.idle":"2026-05-06T07:40:49.264077Z","shell.execute_reply.started":"2026-05-06T07:40:47.074462Z","shell.execute_reply":"2026-05-06T07:40:49.262733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_slides = []\ny_slides = []\nslide_ids = []\n\nfor idx, row in balanced_df.iterrows():\n    image_id = row[\"image_id\"]\n    label = row[\"cancer_label\"]\n    \n    try:\n        tiles = extract_tiles(\n            image_id,\n            tile_size=TILE_SIZE,\n            num_tiles=NUM_TILES,\n            level=1\n        )\n        \n        if len(tiles) == 0:\n            continue\n        \n        while len(tiles) < NUM_TILES:\n            tiles.append(tiles[-1])\n        \n        tiles = tiles[:NUM_TILES]\n\n        tile_array = np.array(\n            [np.array(tile.resize((160, 160))) for tile in tiles],\n            dtype=np.uint8\n        )\n        \n        X_slides.append(tile_array)\n        y_slides.append(label)\n        slide_ids.append(image_id)\n        \n    except Exception as e:\n        print(\"Error:\", image_id, e)\n    \n    if (idx + 1) % 25 == 0:\n        print(f\"Processed {idx + 1}/{len(balanced_df)} slides\")\n\nX_slides = np.array(X_slides, dtype=np.uint8)\ny_slides = np.array(y_slides)\n\nprint(\"X_slides shape:\", X_slides.shape)\nprint(\"y_slides shape:\", y_slides.shape)\nprint(\"Class counts:\", np.bincount(y_slides))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T07:41:24.830317Z","iopub.execute_input":"2026-05-06T07:41:24.830662Z","iopub.status.idle":"2026-05-06T08:03:01.546974Z","shell.execute_reply.started":"2026-05-06T07:41:24.830631Z","shell.execute_reply":"2026-05-06T08:03:01.54589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, f1_score, cohen_kappa_score, roc_auc_score\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.applications import MobileNetV2\nfrom tensorflow.keras.applications.mobilenet_v2 import preprocess_input\nimport tensorflow as tf\n\nNUM_CLASSES = 2\n\nX_train, X_temp, y_train, y_temp = train_test_split(\n    X_slides,\n    y_slides,\n    test_size=0.30,\n    random_state=42,\n    stratify=y_slides\n)\n\nX_val, X_test, y_val, y_test = train_test_split(\n    X_temp,\n    y_temp,\n    test_size=0.50,\n    random_state=42,\n    stratify=y_temp\n)\n\nX_train_p = preprocess_input(X_train.astype(\"float32\"))\nX_val_p = preprocess_input(X_val.astype(\"float32\"))\nX_test_p = preprocess_input(X_test.astype(\"float32\"))\n\ny_train_cat = tf.keras.utils.to_categorical(y_train, NUM_CLASSES)\ny_val_cat = tf.keras.utils.to_categorical(y_val, NUM_CLASSES)\ny_test_cat = tf.keras.utils.to_categorical(y_test, NUM_CLASSES)\n\nprint(\"Train:\", X_train_p.shape)\nprint(\"Val:\", X_val_p.shape)\nprint(\"Test:\", X_test_p.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:05:50.190482Z","iopub.execute_input":"2026-05-06T08:05:50.190851Z","iopub.status.idle":"2026-05-06T08:05:51.205386Z","shell.execute_reply.started":"2026-05-06T08:05:50.190795Z","shell.execute_reply":"2026-05-06T08:05:51.204238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tf.keras.backend.clear_session()\n\nbase_model = MobileNetV2(\n    weights=\"imagenet\",\n    include_top=False,\n    input_shape=(160, 160, 3)\n)\n\nbase_model.trainable = False\n\ninputs = layers.Input(shape=(8, 160, 160, 3))\n\nx = layers.TimeDistributed(base_model)(inputs)\nx = layers.TimeDistributed(layers.GlobalAveragePooling2D())(x)\n\n# Combine the 8 tile features into one slide-level prediction\nx = layers.GlobalAveragePooling1D()(x)\n\nx = layers.Dense(128, activation=\"relu\")(x)\nx = layers.Dropout(0.4)(x)\n\noutputs = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\nslide_model = models.Model(inputs, outputs)\n\nslide_model.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nslide_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:06:02.924514Z","iopub.execute_input":"2026-05-06T08:06:02.925171Z","iopub.status.idle":"2026-05-06T08:06:04.623576Z","shell.execute_reply.started":"2026-05-06T08:06:02.925135Z","shell.execute_reply":"2026-05-06T08:06:04.622649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=8,\n        restore_best_weights=True\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.3,\n        patience=3,\n        min_lr=1e-7\n    )\n]\n\nhistory = slide_model.fit(\n    X_train_p,\n    y_train_cat,\n    validation_data=(X_val_p, y_val_cat),\n    epochs=30,\n    batch_size=4,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:06:19.836981Z","iopub.execute_input":"2026-05-06T08:06:19.837453Z","iopub.status.idle":"2026-05-06T08:15:55.342356Z","shell.execute_reply.started":"2026-05-06T08:06:19.837406Z","shell.execute_reply":"2026-05-06T08:15:55.341293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, f1_score, cohen_kappa_score, roc_auc_score, classification_report, confusion_matrix\nimport numpy as np\n\ntrain_loss, train_acc = slide_model.evaluate(X_train_p, y_train_cat, verbose=0)\nval_loss, val_acc = slide_model.evaluate(X_val_p, y_val_cat, verbose=0)\ntest_loss, test_acc = slide_model.evaluate(X_test_p, y_test_cat, verbose=0)\n\ny_pred_probs = slide_model.predict(X_test_p)\ny_pred = np.argmax(y_pred_probs, axis=1)\n\nf1 = f1_score(y_test, y_pred, average=\"weighted\")\nkappa = cohen_kappa_score(y_test, y_pred)\n\nroc_auc = roc_auc_score(y_test, y_pred_probs[:, 1])\n\nprint(\"Training Accuracy:\", train_acc)\nprint(\"Validation Accuracy:\", val_acc)\nprint(\"Test Accuracy:\", test_acc)\nprint(\"F1 Measure:\", f1)\nprint(\"Kappa:\", kappa)\nprint(\"ROC AUC:\", roc_auc)\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(y_test, y_pred, target_names=[\"No Cancer\", \"Cancer\"]))\n\nprint(\"\\nConfusion Matrix:\")\nprint(confusion_matrix(y_test, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:27:11.74113Z","iopub.execute_input":"2026-05-06T08:27:11.741534Z","iopub.status.idle":"2026-05-06T08:28:17.99805Z","shell.execute_reply.started":"2026-05-06T08:27:11.741501Z","shell.execute_reply":"2026-05-06T08:28:17.996985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.applications import (\n    MobileNetV2,\n    ResNet50,\n    ResNet101,\n    VGG16,\n    VGG19,\n    DenseNet121,\n    DenseNet169,\n    EfficientNetB0,\n    InceptionV3,\n    InceptionResNetV2\n)\n\nfrom tensorflow.keras import layers, models\nimport tensorflow as tf\nimport numpy as np\n\nfrom sklearn.metrics import f1_score, cohen_kappa_score, roc_auc_score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:31:23.119986Z","iopub.execute_input":"2026-05-06T08:31:23.120352Z","iopub.status.idle":"2026-05-06T08:31:23.126358Z","shell.execute_reply.started":"2026-05-06T08:31:23.120318Z","shell.execute_reply":"2026-05-06T08:31:23.125304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = []\n\ndef build_slide_model(backbone_fn, model_name, input_shape=(160, 160, 3), num_tiles=8, num_classes=2):\n    tf.keras.backend.clear_session()\n    \n    base_model = backbone_fn(\n        weights=\"imagenet\",\n        include_top=False,\n        input_shape=input_shape\n    )\n    \n    base_model.trainable = False\n    \n    inputs = layers.Input(shape=(num_tiles, input_shape[0], input_shape[1], 3))\n    \n    x = layers.TimeDistributed(base_model)(inputs)\n    x = layers.TimeDistributed(layers.GlobalAveragePooling2D())(x)\n    \n    # Slide-level aggregation\n    x = layers.GlobalAveragePooling1D()(x)\n    \n    x = layers.Dense(128, activation=\"relu\")(x)\n    x = layers.Dropout(0.4)(x)\n    \n    outputs = layers.Dense(num_classes, activation=\"softmax\")(x)\n    \n    model = models.Model(inputs, outputs, name=model_name)\n    \n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n        loss=\"categorical_crossentropy\",\n        metrics=[\"accuracy\"]\n    )\n    \n    return model\n\n\ndef train_and_evaluate(backbone_fn, model_name, epochs=20, batch_size=4):\n    print(f\"\\n==============================\")\n    print(f\"Training: {model_name}\")\n    print(f\"==============================\")\n    \n    model = build_slide_model(backbone_fn, model_name)\n    \n    callbacks = [\n        tf.keras.callbacks.EarlyStopping(\n            monitor=\"val_accuracy\",\n            patience=6,\n            restore_best_weights=True\n        ),\n        tf.keras.callbacks.ReduceLROnPlateau(\n            monitor=\"val_loss\",\n            factor=0.3,\n            patience=3,\n            min_lr=1e-7\n        )\n    ]\n    \n    history = model.fit(\n        X_train_p,\n        y_train_cat,\n        validation_data=(X_val_p, y_val_cat),\n        epochs=epochs,\n        batch_size=batch_size,\n        callbacks=callbacks,\n        verbose=1\n    )\n    \n    train_loss, train_acc = model.evaluate(X_train_p, y_train_cat, verbose=0)\n    val_loss, val_acc = model.evaluate(X_val_p, y_val_cat, verbose=0)\n    test_loss, test_acc = model.evaluate(X_test_p, y_test_cat, verbose=0)\n    \n    y_pred_probs = model.predict(X_test_p)\n    y_pred = np.argmax(y_pred_probs, axis=1)\n    \n    f1 = f1_score(y_test, y_pred, average=\"weighted\")\n    kappa = cohen_kappa_score(y_test, y_pred)\n    roc_auc = roc_auc_score(y_test, y_pred_probs[:, 1])\n    \n    result = {\n        \"CLASSIFIER\": model_name,\n        \"TRAINING ACCURACY\": train_acc,\n        \"VALIDATION ACCURACY\": val_acc,\n        \"TEST ACCURACY\": test_acc,\n        \"F1 Measure\": f1,\n        \"KAPPA\": kappa,\n        \"ROC area\": roc_auc\n    }\n    \n    results.append(result)\n    \n    print(\"\\nResult:\")\n    for key, value in result.items():\n        print(key, \":\", value)\n    \n    return model, history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:32:12.625733Z","iopub.execute_input":"2026-05-06T08:32:12.626151Z","iopub.status.idle":"2026-05-06T08:32:12.642862Z","shell.execute_reply.started":"2026-05-06T08:32:12.626118Z","shell.execute_reply":"2026-05-06T08:32:12.641579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Clear old failed results, then add your already-completed MobileNetV2 result\nresults = []\n\nresults.append({\n    \"CLASSIFIER\": \"MobileNetV2_Slide_Level_Binary\",\n    \"TRAINING ACCURACY\": 0.8485714197158813,\n    \"VALIDATION ACCURACY\": 0.800000011920929,\n    \"TEST ACCURACY\": 0.7866666913032532,\n    \"F1 Measure\": 0.7865908013276434,\n    \"KAPPA\": 0.5735607675906184,\n    \"ROC area\": 0.879800853485064\n})\n\n# Train DenseNet121\nmodel_densenet121, history_densenet121 = train_and_evaluate(\n    DenseNet121,\n    \"DenseNet121_Slide_Level_Binary\",\n    epochs=20,\n    batch_size=4\n)\n\n# Train ResNet50\nmodel_resnet50, history_resnet50 = train_and_evaluate(\n    ResNet50,\n    \"ResNet50_Slide_Level_Binary\",\n    epochs=20,\n    batch_size=4\n)\n\n# Show results table\nresults_df = pd.DataFrame(results)\nresults_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T08:39:02.507801Z","iopub.execute_input":"2026-05-06T08:39:02.508525Z","iopub.status.idle":"2026-05-06T10:01:49.156933Z","shell.execute_reply.started":"2026-05-06T08:39:02.508491Z","shell.execute_reply":"2026-05-06T10:01:49.155892Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nnp.save(\"/kaggle/working/X_slides.npy\", X_slides)\nnp.save(\"/kaggle/working/y_slides.npy\", y_slides)\n\nnp.save(\"/kaggle/working/X_train_p.npy\", X_train_p)\nnp.save(\"/kaggle/working/X_val_p.npy\", X_val_p)\nnp.save(\"/kaggle/working/X_test_p.npy\", X_test_p)\n\nnp.save(\"/kaggle/working/y_train.npy\", y_train)\nnp.save(\"/kaggle/working/y_val.npy\", y_val)\nnp.save(\"/kaggle/working/y_test.npy\", y_test)\n\nnp.save(\"/kaggle/working/y_train_cat.npy\", y_train_cat)\nnp.save(\"/kaggle/working/y_val_cat.npy\", y_val_cat)\nnp.save(\"/kaggle/working/y_test_cat.npy\", y_test_cat)\n\nresults_df = pd.DataFrame(results)\nresults_df.to_csv(\"/kaggle/working/model_results.csv\", index=False)\n\nprint(results_df)\nprint(\"Saved everything.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T10:20:09.042442Z","iopub.execute_input":"2026-05-06T10:20:09.044543Z","iopub.status.idle":"2026-05-06T10:20:11.667642Z","shell.execute_reply.started":"2026-05-06T10:20:09.044497Z","shell.execute_reply":"2026-05-06T10:20:11.666331Z"}},"outputs":[],"execution_count":null}]}