{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"}],"dockerImageVersionId":30840,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Install Required Libraries\n# --------------------------","metadata":{}},{"cell_type":"code","source":"!pip install matplotlib seaborn scikit-learn","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:46:10.066745Z","iopub.execute_input":"2025-02-08T17:46:10.067115Z","iopub.status.idle":"2025-02-08T17:46:14.290708Z","shell.execute_reply.started":"2025-02-08T17:46:10.067082Z","shell.execute_reply":"2025-02-08T17:46:14.289811Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [](http://)Import Libraries\n# ---------------","metadata":{}},{"cell_type":"code","source":"import os\nimport time\nimport json\nimport glob\nimport random\nimport collections\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport pydicom as dicom\nfrom tqdm import tqdm\nfrom copy import deepcopy\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix\nimport joblib\nimport pydicom as dicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, applications, optimizers, callbacks\nfrom tensorflow.keras import backend as K\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n# import os\n# import pandas as pd\n# import matplotlib.pyplot as plt\nfrom keras.applications import ResNet50\nfrom keras.layers import Conv2D, UpSampling2D, concatenate, GlobalAveragePooling2D, Dense, BatchNormalization, Dropout\nfrom keras.models import Model\nfrom keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom keras.optimizers import Adam\n# from sklearn.model_selection import train_test_split\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:46:14.292016Z","iopub.execute_input":"2025-02-08T17:46:14.292287Z","iopub.status.idle":"2025-02-08T17:46:27.296999Z","shell.execute_reply.started":"2025-02-08T17:46:14.292265Z","shell.execute_reply":"2025-02-08T17:46:27.296346Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Loading\n# ------------","metadata":{}},{"cell_type":"code","source":"# Define the path to the training data\ntrain_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/'\n\n# Load CSV files containing metadata and labels\ntrain = pd.read_csv(os.path.join(train_path, 'train.csv'))\nlabel = pd.read_csv(os.path.join(train_path, 'train_label_coordinates.csv'))\ntrain_desc = pd.read_csv(os.path.join(train_path, 'train_series_descriptions.csv'))\ntest_desc = pd.read_csv(os.path.join(train_path, 'test_series_descriptions.csv'))\nsub = pd.read_csv(os.path.join(train_path, 'sample_submission.csv'))\n\n# Display the first few rows of each dataframe\nprint(\"Test Descriptions:\")\nprint(test_desc.head(5))\n\nprint(\"\\nTrain Data:\")\nprint(train.head(5))\n\nprint(\"\\nTrain Series Descriptions:\")\nprint(train_desc.head(5))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:46:27.298161Z","iopub.execute_input":"2025-02-08T17:46:27.298699Z","iopub.status.idle":"2025-02-08T17:46:27.469834Z","shell.execute_reply.started":"2025-02-08T17:46:27.298675Z","shell.execute_reply":"2025-02-08T17:46:27.469030Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate Image Paths\n# ---------------------\ndef generate_image_paths(df, data_dir):\n    \"\"\"\n    Generate a list of image file paths based on study_id and series_id.\n    \n    Parameters:\n        df (DataFrame): DataFrame containing 'study_id' and 'series_id' columns.\n        data_dir (str): Base directory where images are stored.\n    \n    Returns:\n        List[str]: List of sorted image file paths.\n    \"\"\"\n    image_paths = []\n    for study_id, series_id in zip(df['study_id'], df['series_id']):\n        study_dir = os.path.join(data_dir, str(study_id))\n        series_dir = os.path.join(study_dir, str(series_id))\n        # Sort images by filename to maintain DICOM sequence\n        images = sorted(os.listdir(series_dir))\n        image_paths.extend([os.path.join(series_dir, img) for img in images])\n    return image_paths\n\n# Generate image paths for training and testing datasets\ntrain_image_paths = generate_image_paths(train_desc, os.path.join(train_path, 'train_images'))\ntest_image_paths = generate_image_paths(test_desc, os.path.join(train_path, 'test_images'))\n\n# Example usage\nprint(\"\\nSample Train Image Path:\", train_image_paths[2])\nprint(\"Number of Train Descriptions:\", len(train_desc))\nprint(\"Number of Train Image Paths:\", len(train_image_paths))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:46:27.470857Z","iopub.execute_input":"2025-02-08T17:46:27.471145Z","iopub.status.idle":"2025-02-08T17:47:22.025253Z","shell.execute_reply.started":"2025-02-08T17:46:27.471113Z","shell.execute_reply":"2025-02-08T17:47:22.024536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Display DICOM Images\n# ---------------------\nimport pydicom \ndef display_dicom_images(image_paths, num_images=3):\n    \"\"\"\n    Display a specified number of DICOM images.\n    \n    Parameters:\n        image_paths (List[str]): List of DICOM image file paths.\n        num_images (int): Number of images to display.\n    \"\"\"\n    plt.figure(figsize=(15, 5))\n    for i, path in enumerate(image_paths[:num_images]):\n        ds = pydicom.dcmread(path)\n        plt.subplot(1, num_images, i+1)\n        plt.imshow(ds.pixel_array, cmap=plt.cm.bone)\n        plt.title(f\"Image {i+1}\")\n        plt.axis('off')\n    plt.show()\n\n# Display the first three DICOM images from training data\ndisplay_dicom_images(train_image_paths)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:22.025979Z","iopub.execute_input":"2025-02-08T17:47:22.026237Z","iopub.status.idle":"2025-02-08T17:47:22.774997Z","shell.execute_reply.started":"2025-02-08T17:47:22.026215Z","shell.execute_reply":"2025-02-08T17:47:22.774104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Display DICOM Images with Coordinates\n# -------------------------------------\ndef display_dicom_with_coordinates(image_paths, label_df):\n    \"\"\"\n    Display DICOM images with annotated coordinates.\n    \n    Parameters:\n        image_paths (List[str]): List of DICOM image file paths.\n        label_df (DataFrame): DataFrame containing label coordinates.\n    \"\"\"\n    fig, axs = plt.subplots(1, len(image_paths), figsize=(18, 6))\n\n    for idx, path in enumerate(image_paths):\n        study_id = int(path.split('/')[-3])\n        series_id = int(path.split('/')[-2])\n\n        # Filter labels for the current study and series\n        filtered_labels = label_df[\n            (label_df['study_id'] == study_id) & \n            (label_df['series_id'] == series_id)\n        ]\n\n        # Read DICOM image\n        ds = pydicom.dcmread(path)\n\n        # Plot DICOM image\n        axs[idx].imshow(ds.pixel_array, cmap='gray')\n        axs[idx].set_title(f\"Study ID: {study_id}, Series ID: {series_id}\")\n        axs[idx].axis('off')\n\n        # Plot coordinates\n        for _, row in filtered_labels.iterrows():\n            axs[idx].plot(row['x'], row['y'], 'ro', markersize=5)\n\n    plt.tight_layout()\n    plt.show()\n\ndef load_dicom_files(path_to_folder):\n    \"\"\"\n    Load and sort DICOM files from a specified folder.\n    \n    Parameters:\n        path_to_folder (str): Directory containing DICOM files.\n    \n    Returns:\n        List[str]: Sorted list of DICOM file paths.\n    \"\"\"\n    files = [os.path.join(path_to_folder, f) for f in os.listdir(path_to_folder) if f.endswith('.dcm')]\n    files.sort(key=lambda x: int(os.path.splitext(os.path.basename(x))[0].split('-')[-1]))\n    return files\n\n# Example: Display DICOM images with coordinates for a specific study\nstudy_id = \"100206310\"\nstudy_folder = os.path.join(train_path, 'train_images', study_id)\n\nimage_paths = []\nfor series_folder in os.listdir(study_folder):\n    series_folder_path = os.path.join(study_folder, series_folder)\n    dicom_files = load_dicom_files(series_folder_path)\n    if dicom_files:\n        image_paths.append(dicom_files[0])  # Add the first image from each series\n\ndisplay_dicom_with_coordinates(image_paths, label)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:22.775776Z","iopub.execute_input":"2025-02-08T17:47:22.776001Z","iopub.status.idle":"2025-02-08T17:47:23.448379Z","shell.execute_reply.started":"2025-02-08T17:47:22.775972Z","shell.execute_reply":"2025-02-08T17:47:23.447183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data Reshaping and Merging\n# --------------------------\ndef reshape_row(row):\n    \"\"\"\n    Reshape a single row of the DataFrame to separate conditions, levels, and severities.\n    \n    Parameters:\n        row (Series): A row from the DataFrame.\n    \n    Returns:\n        DataFrame: Reshaped DataFrame.\n    \"\"\"\n    data = {'study_id': [], 'condition': [], 'level': [], 'severity': []}\n\n    for column, value in row.items():\n        if column not in ['study_id', 'series_id', 'instance_number', 'x', 'y', 'series_description']:\n            parts = column.split('_')\n            condition = ' '.join([word.capitalize() for word in parts[:-2]])\n            level = parts[-2].capitalize() + '/' + parts[-1].capitalize()\n            data['study_id'].append(row['study_id'])\n            data['condition'].append(condition)\n            data['level'].append(level)\n            data['severity'].append(value)\n\n    return pd.DataFrame(data)\n\n# Reshape the training DataFrame\nnew_train_df = pd.concat([reshape_row(row) for _, row in train.iterrows()], ignore_index=True)\n\n# Display the first few rows of the reshaped DataFrame\nprint(\"\\nReshaped Train Data:\")\nprint(new_train_df.head(5))\n\n# Print columns for verification\nprint(\"\\nColumns in new_train_df:\")\nprint(\", \".join(new_train_df.columns))\n\nprint(\"\\nColumns in label:\")\nprint(\", \".join(label.columns))\n\nprint(\"\\nColumns in test_desc:\")\nprint(\", \".join(test_desc.columns))\n\nprint(\"\\nColumns in sub:\")\nprint(\", \".join(sub.columns))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:23.449463Z","iopub.execute_input":"2025-02-08T17:47:23.449885Z","iopub.status.idle":"2025-02-08T17:47:24.328971Z","shell.execute_reply.started":"2025-02-08T17:47:23.449845Z","shell.execute_reply":"2025-02-08T17:47:24.327871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Merge DataFrames\n# -----------------\n# Merge reshaped training data with label coordinates\nmerged_df = pd.merge(new_train_df, label, on=['study_id', 'condition', 'level'], how='inner')\n\n# Further merge with training series descriptions\nfinal_merged_df = pd.merge(merged_df, train_desc, on=['series_id', 'study_id'], how='inner')\n\n# Display the first few rows of the final merged DataFrame\nprint(\"\\nFinal Merged Data:\")\nprint(final_merged_df.head(5))\n\n# Example Queries\nprint(\"\\nEntries for Study ID 100206310:\")\nprint(final_merged_df[final_merged_df['study_id'] == 100206310].sort_values(['x','y'], ascending=True))\n\nprint(\"\\nEntries for Series ID 1012284084:\")\nprint(final_merged_df[final_merged_df['series_id'] == 1012284084].sort_values(\"instance_number\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.331398Z","iopub.execute_input":"2025-02-08T17:47:24.331669Z","iopub.status.idle":"2025-02-08T17:47:24.394795Z","shell.execute_reply.started":"2025-02-08T17:47:24.331645Z","shell.execute_reply":"2025-02-08T17:47:24.393994Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Merge DataFrames\n# -----------------","metadata":{}},{"cell_type":"code","source":"# Merge reshaped training data with label coordinates\nmerged_df = pd.merge(new_train_df, label, on=['study_id', 'condition', 'level'], how='inner')\n\n# Further merge with training series descriptions\nfinal_merged_df = pd.merge(merged_df, train_desc, on=['series_id', 'study_id'], how='inner')\n\n# Display the first few rows of the final merged DataFrame\nprint(\"\\nFinal Merged Data:\")\nprint(final_merged_df.head(5))\n\n# Example Queries\nprint(\"\\nEntries for Study ID 100206310:\")\nprint(final_merged_df[final_merged_df['study_id'] == 100206310].sort_values(['x','y'], ascending=True))\n\nprint(\"\\nEntries for Series ID 1012284084:\")\nprint(final_merged_df[final_merged_df['series_id'] == 1012284084].sort_values(\"instance_number\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.396415Z","iopub.execute_input":"2025-02-08T17:47:24.396709Z","iopub.status.idle":"2025-02-08T17:47:24.449328Z","shell.execute_reply.started":"2025-02-08T17:47:24.396685Z","shell.execute_reply":"2025-02-08T17:47:24.448367Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Filter and Sort Data\n# ---------------------","metadata":{}},{"cell_type":"code","source":"\n# Filter the DataFrame for a specific study_id and sort by instance_number\nfiltered_df = final_merged_df[final_merged_df['study_id'] == 1013589491].sort_values(\"instance_number\")\n\n# Display the filtered DataFrame\nprint(\"\\nFiltered DataFrame for Study ID 1013589491:\")\nprint(filtered_df)\n\n# Sort the final merged DataFrame by study_id, series_id, and series_description\nsorted_final_merged_df = final_merged_df[\n    final_merged_df['study_id'] == 1013589491\n].sort_values(by=['series_id', 'series_description', 'instance_number'])\n\nprint(\"\\nSorted Final Merged DataFrame:\")\nprint(sorted_final_merged_df)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.450401Z","iopub.execute_input":"2025-02-08T17:47:24.450670Z","iopub.status.idle":"2025-02-08T17:47:24.468292Z","shell.execute_reply.started":"2025-02-08T17:47:24.450647Z","shell.execute_reply":"2025-02-08T17:47:24.467325Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Add Row ID and Image Path Columns\n# ---------------------------------","metadata":{}},{"cell_type":"code","source":"# Create the 'row_id' column by combining study_id, condition, and level\nfinal_merged_df['row_id'] = (\n    final_merged_df['study_id'].astype(str) + '_' +\n    final_merged_df['condition'].str.lower().str.replace(' ', '_') + '_' +\n    final_merged_df['level'].str.lower().str.replace('/', '_')\n)\n\n# Create the 'image_path' column based on directory structure\nfinal_merged_df['image_path'] = (\n    os.path.join(train_path, 'train_images') + '/' +\n    final_merged_df['study_id'].astype(str) + '/' +\n    final_merged_df['series_id'].astype(str) + '/' +\n    final_merged_df['instance_number'].astype(str) + '.dcm'\n)\n\n# Display the updated DataFrame\nprint(\"\\nUpdated Final Merged DataFrame:\")\nprint(final_merged_df.head(5))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.469192Z","iopub.execute_input":"2025-02-08T17:47:24.469530Z","iopub.status.idle":"2025-02-08T17:47:24.623722Z","shell.execute_reply.started":"2025-02-08T17:47:24.469494Z","shell.execute_reply":"2025-02-08T17:47:24.622965Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Severity Distribution\n# ---------------------","metadata":{}},{"cell_type":"code","source":"# Calculate the number of entries for each severity level\nnormal_mild_count = final_merged_df[final_merged_df[\"severity\"] == \"Normal/Mild\"].shape[0]\nmoderate_count = final_merged_df[final_merged_df[\"severity\"] == \"Moderate\"].shape[0]\nsevere_count = final_merged_df[final_merged_df[\"severity\"] == \"Severe\"].shape[0]\n\nprint(f\"\\nNormal/Mild Count: {normal_mild_count}\")\nprint(f\"Moderate Count: {moderate_count}\")\nprint(f\"Severe Count: {severe_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.624544Z","iopub.execute_input":"2025-02-08T17:47:24.624888Z","iopub.status.idle":"2025-02-08T17:47:24.650872Z","shell.execute_reply.started":"2025-02-08T17:47:24.624854Z","shell.execute_reply":"2025-02-08T17:47:24.650215Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Expand Test Descriptions\n# ------------------------","metadata":{}},{"cell_type":"code","source":"# Define the base path for test images\nbase_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_images/'\n\ndef get_image_paths(row):\n    \"\"\"\n    Retrieve all image file paths for a given series.\n    \n    Parameters:\n        row (Series): A row from the test_desc DataFrame.\n    \n    Returns:\n        List[str]: List of image file paths.\n    \"\"\"\n    series_path = os.path.join(base_path, str(row['study_id']), str(row['series_id']))\n    if os.path.exists(series_path):\n        return [\n            os.path.join(series_path, f) \n            for f in os.listdir(series_path) \n            if os.path.isfile(os.path.join(series_path, f))\n        ]\n    return []\n\n# Mapping of series_description to conditions\ncondition_mapping = {\n    'Sagittal T1': {\n        'left': 'left_neural_foraminal_narrowing', \n        'right': 'right_neural_foraminal_narrowing'\n    },\n    'Axial T2': {\n        'left': 'left_subarticular_stenosis', \n        'right': 'right_subarticular_stenosis'\n    },\n    'Sagittal T2/STIR': 'spinal_canal_stenosis'\n}\n\n# Expand the test descriptions by adding new rows for each image path and condition\nexpanded_rows = []\n\nfor index, row in test_desc.iterrows():\n    image_paths = get_image_paths(row)\n    conditions = condition_mapping.get(row['series_description'], {})\n    \n    # Handle single or multiple conditions\n    if isinstance(conditions, str):\n        conditions = {'left': conditions, 'right': conditions}\n    \n    for side, condition in conditions.items():\n        for image_path in image_paths:\n            expanded_rows.append({\n                'study_id': row['study_id'],\n                'series_id': row['series_id'],\n                'series_description': row['series_description'],\n                'image_path': image_path,\n                'condition': condition,\n                'row_id': f\"{row['study_id']}_{condition}\"\n            })\n\n# Create a new DataFrame from the expanded rows\nexpanded_test_desc = pd.DataFrame(expanded_rows)\n\n# Display the first few rows of the expanded test descriptions\nprint(\"\\nExpanded Test Descriptions:\")\nprint(expanded_test_desc.head(5))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.651674Z","iopub.execute_input":"2025-02-08T17:47:24.651910Z","iopub.status.idle":"2025-02-08T17:47:24.754938Z","shell.execute_reply.started":"2025-02-08T17:47:24.651879Z","shell.execute_reply":"2025-02-08T17:47:24.754253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Update Severity Labels\n# -----------------------\n# Map severity labels to simplified categories\nfinal_merged_df['severity'] = final_merged_df['severity'].map({\n    'Normal/Mild': 'normal_mild', \n    'Moderate': 'moderate', \n    'Severe': 'severe'\n})\n\n# Assign train and test data\ntrain_data = final_merged_df\ntest_data = expanded_test_desc\n\n# Display sample data\nprint(\"\\nSample Train Data:\")\nprint(train_data.head(10))\n\nprint(\"\\nSample Test Data:\")\nprint(test_data.head(10))\n\n# Display the shape of the training data\nprint(\"\\nTrain Data Shape:\", train_data.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.755861Z","iopub.execute_input":"2025-02-08T17:47:24.756197Z","iopub.status.idle":"2025-02-08T17:47:24.771404Z","shell.execute_reply.started":"2025-02-08T17:47:24.756166Z","shell.execute_reply":"2025-02-08T17:47:24.770635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verify File Paths\n# -----------------\ndef check_exists(path):\n    \"\"\"\n    Check if a given file or directory path exists.\n    \n    Parameters:\n        path (str): File or directory path.\n    \n    Returns:\n        bool: True if path exists, False otherwise.\n    \"\"\"\n    return os.path.exists(path)\n\ndef check_study_id(row):\n    \"\"\"\n    Check if the study_id directory exists.\n    \n    Parameters:\n        row (Series): A row from the DataFrame.\n    \n    Returns:\n        bool: True if study_id directory exists, False otherwise.\n    \"\"\"\n    study_id = row['study_id']\n    path = os.path.join(train_path, 'train_images', str(study_id))\n    return check_exists(path)\n\ndef check_series_id(row):\n    \"\"\"\n    Check if the series_id directory exists.\n    \n    Parameters:\n        row (Series): A row from the DataFrame.\n    \n    Returns:\n        bool: True if series_id directory exists, False otherwise.\n    \"\"\"\n    study_id = row['study_id']\n    series_id = row['series_id']\n    path = os.path.join(train_path, 'train_images', str(study_id), str(series_id))\n    return check_exists(path)\n\ndef check_image_exists(row):\n    \"\"\"\n    Check if the image file exists.\n    \n    Parameters:\n        row (Series): A row from the DataFrame.\n    \n    Returns:\n        bool: True if image file exists, False otherwise.\n    \"\"\"\n    image_path = row['image_path']\n    return check_exists(image_path)\n\n# Apply existence checks to the training data\ntrain_data['study_id_exists'] = train_data.apply(check_study_id, axis=1)\ntrain_data['series_id_exists'] = train_data.apply(check_series_id, axis=1)\ntrain_data['image_exists'] = train_data.apply(check_image_exists, axis=1)\n\n# Filter training data to include only existing paths\ntrain_data = train_data[\n    train_data['study_id_exists'] & \n    train_data['series_id_exists'] & \n    train_data['image_exists']\n]\n\n# Display the shape after filtering\nprint(\"\\nTrain Data Shape after Filtering:\", train_data.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:24.772171Z","iopub.execute_input":"2025-02-08T17:47:24.772394Z","iopub.status.idle":"2025-02-08T17:47:55.097094Z","shell.execute_reply.started":"2025-02-08T17:47:24.772374Z","shell.execute_reply":"2025-02-08T17:47:55.096209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load and Display Sample Images\n# ------------------------------\ndef load_dicom(path):\n    \"\"\"\n    Load a DICOM image and normalize its pixel data.\n    \n    Parameters:\n        path (str): Path to the DICOM file.\n    \n    Returns:\n        np.ndarray: Normalized image data as uint8.\n    \"\"\"\n    dicom = pydicom.dcmread(path)\n    data = dicom.pixel_array\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)  # Normalize the image\n    return (data * 255).astype(np.uint8)\n\n# Select two random samples from the training data\nimages = []\nrow_ids = []\nselected_indices = random.sample(range(len(train_data)), 2)\nfor i in selected_indices:\n    image = load_dicom(train_data.iloc[i]['image_path'])\n    images.append(image)\n    row_ids.append(train_data.iloc[i]['row_id'])\n\n# Plot the selected images\nfig, ax = plt.subplots(1, 2, figsize=(8, 4))\nfor i in range(2):\n    ax[i].imshow(images[i], cmap='gray')\n    ax[i].set_title(f'Row ID: {row_ids[i]}', fontsize=8)\n    ax[i].axis('off')\nplt.tight_layout()\nplt.show()\n\n# Remove any rows with missing values\ntrain_data = train_data.dropna()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.098128Z","iopub.execute_input":"2025-02-08T17:47:55.098454Z","iopub.status.idle":"2025-02-08T17:47:55.419023Z","shell.execute_reply.started":"2025-02-08T17:47:55.098421Z","shell.execute_reply":"2025-02-08T17:47:55.418121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Visualization Functions\n# -------------------------------\ndef plot_confusion_matrix(cm, classes, title):\n    \"\"\"\n    Plot a confusion matrix using Seaborn heatmap.\n    \n    Parameters:\n        cm (ndarray): Confusion matrix.\n        classes (List[str]): List of class names.\n        title (str): Title of the plot.\n    \"\"\"\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=classes, yticklabels=classes, cbar=False)\n    plt.title(title)\n    plt.xlabel('Predicted Label')\n    plt.ylabel('True Label')\n    plt.xticks(rotation=45)\n    plt.yticks(rotation=45)\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.419968Z","iopub.execute_input":"2025-02-08T17:47:55.420295Z","iopub.status.idle":"2025-02-08T17:47:55.425263Z","shell.execute_reply.started":"2025-02-08T17:47:55.420265Z","shell.execute_reply":"2025-02-08T17:47:55.424468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data Loading and Preprocessing Functions\n# ----------------------------------------\ndef load_dicom_tf(path):\n    \"\"\"\n    Load a DICOM image within a TensorFlow graph.\n    \n    Parameters:\n        path (tf.Tensor): Path tensor.\n    \n    Returns:\n        tf.Tensor: Image data as float32.\n    \"\"\"\n    dicom = pydicom.dcmread(path.numpy().decode('utf-8'))\n    data = dicom.pixel_array\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    return data.astype(np.float32)\n\ndef preprocess_image(image, label=None):\n    \"\"\"\n    Preprocess the image by resizing and converting to RGB.\n    \n    Parameters:\n        image (tf.Tensor): Grayscale image tensor.\n        label (tf.Tensor, optional): Label tensor.\n    \n    Returns:\n        Tuple: Preprocessed image and label.\n    \"\"\"\n    image = tf.expand_dims(image, -1)  # Add channel dimension\n    image = tf.image.resize(image, [224, 224])  # Resize to 224x224\n    image = tf.image.grayscale_to_rgb(image)  # Convert to RGB\n    return (image, label) if label is not None else image\n\ndef create_dataset(df, batch_size=64, is_test=False):\n    \"\"\"\n    Create a TensorFlow dataset from a DataFrame.\n    \n    Parameters:\n        df (DataFrame): DataFrame containing image paths and labels.\n        batch_size (int): Batch size.\n        is_test (bool): Flag indicating whether it's test data.\n    \n    Returns:\n        tf.data.Dataset: Prepared dataset.\n    \"\"\"\n    if is_test:\n        def load_wrapper(path):\n            image = tf.py_function(load_dicom_tf, [path], tf.float32)\n            image.set_shape((None, None))\n            return preprocess_image(image)\n        \n        dataset = tf.data.Dataset.from_tensor_slices(df['image_path'])\n        dataset = dataset.map(load_wrapper, num_parallel_calls=tf.data.AUTOTUNE)\n    else:\n        def load_wrapper(path, label):\n            image = tf.py_function(load_dicom_tf, [path], tf.float32)\n            image.set_shape((None, None))\n            return preprocess_image(image, label)\n        \n        labels = df['severity'].map({'normal_mild': 0, 'moderate': 1, 'severe': 2}).astype(np.int32)\n        dataset = tf.data.Dataset.from_tensor_slices((df['image_path'], labels))\n        dataset = dataset.map(load_wrapper, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    dataset = dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return dataset\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.426146Z","iopub.execute_input":"2025-02-08T17:47:55.426443Z","iopub.status.idle":"2025-02-08T17:47:55.442284Z","shell.execute_reply.started":"2025-02-08T17:47:55.426412Z","shell.execute_reply":"2025-02-08T17:47:55.441499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Custom Loss Function\n# ---------------------------\ndef weighted_log_loss(y_true, y_pred):\n    \"\"\"\n    Compute a weighted sparse categorical cross-entropy loss.\n    \n    Parameters:\n        y_true (Tensor): True labels.\n        y_pred (Tensor): Predicted logits.\n    \n    Returns:\n        Tensor: Weighted loss.\n    \"\"\"\n    weights = tf.gather([1.0, 2.0, 4.0], tf.cast(y_true, tf.int32))\n    loss = tf.keras.losses.sparse_categorical_crossentropy(y_true, y_pred)\n    return tf.reduce_mean(loss * weights)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.443005Z","iopub.execute_input":"2025-02-08T17:47:55.443327Z","iopub.status.idle":"2025-02-08T17:47:55.462246Z","shell.execute_reply.started":"2025-02-08T17:47:55.443294Z","shell.execute_reply":"2025-02-08T17:47:55.461385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Model Building Function\n# ------------------------------\ndef build_resnet50(num_classes=3):\n    \"\"\"\n    Build a ResNet50-based model for classification.\n    \n    Parameters:\n        num_classes (int): Number of output classes.\n    \n    Returns:\n        tf.keras.Model: Compiled ResNet50 model.\n    \"\"\"\n    base_model = applications.ResNet50(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(224, 224, 3)\n    )\n\n    # Freeze all layers except for conv4_block and conv5_block\n    for layer in base_model.layers:\n        if 'conv4_block' in layer.name or 'conv5_block' in layer.name:\n            layer.trainable = True\n        else:\n            layer.trainable = False\n\n    inputs = tf.keras.Input(shape=(224, 224, 3))\n    x = base_model(inputs)\n    x = layers.GlobalAveragePooling2D()(x)\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n\n    model = tf.keras.Model(inputs, outputs)\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.463072Z","iopub.execute_input":"2025-02-08T17:47:55.463310Z","iopub.status.idle":"2025-02-08T17:47:55.475887Z","shell.execute_reply.started":"2025-02-08T17:47:55.463291Z","shell.execute_reply":"2025-02-08T17:47:55.475110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Training Function\n# ------------------------\ndef train_model(model, train_dataset, val_dataset, series_name):\n    \"\"\"\n    Compile and train the model.\n    \n    Parameters:\n        model (tf.keras.Model): The model to train.\n        train_dataset (tf.data.Dataset): Training dataset.\n        val_dataset (tf.data.Dataset): Validation dataset.\n        series_name (str): Name of the series for checkpointing.\n    \n    Returns:\n        History: Training history.\n    \"\"\"\n    model.compile(\n        optimizer=optimizers.Adam(0.001),\n        loss=weighted_log_loss,\n        metrics=['accuracy']\n    )\n\n    callbacks_list = [\n        callbacks.EarlyStopping(patience=10, restore_best_weights=True),\n        callbacks.ModelCheckpoint(f'best_{series_name}.keras', save_best_only=True),\n        # callbacks.ReduceLROnPlateau(factor=0.1, patience=1)\n    ]\n\n    history = model.fit(\n        train_dataset,\n        validation_data=val_dataset,\n        epochs=1,\n        callbacks=callbacks_list\n    )\n    return history\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.476699Z","iopub.execute_input":"2025-02-08T17:47:55.476995Z","iopub.status.idle":"2025-02-08T17:47:55.489753Z","shell.execute_reply.started":"2025-02-08T17:47:55.476965Z","shell.execute_reply":"2025-02-08T17:47:55.489085Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define Evaluation Function\n# --------------------------\ndef evaluate_model(model, dataset):\n    \"\"\"\n    Evaluate the model on a dataset and compute metrics.\n    \n    Parameters:\n        model (tf.keras.Model): Trained model.\n        dataset (tf.data.Dataset): Dataset for evaluation.\n    \n    Returns:\n        dict: Dictionary containing precision, recall, f1-score, and confusion matrix.\n    \"\"\"\n    y_true = []\n    y_pred = []\n    for batch in dataset:\n        # Extract images and labels from the batch\n        images, labels = batch[0], batch[1]\n        y_true.extend(labels.numpy())\n        preds = model.predict(images)\n        y_pred.extend(np.argmax(preds, axis=1))\n\n    # Remove NaN values if any\n    y_true = np.array(y_true)\n    y_pred = np.array(y_pred)\n    valid_indices = ~np.isnan(y_true)\n    y_true = y_true[valid_indices]\n    y_pred = y_pred[valid_indices]\n\n    return {\n        'precision': precision_score(y_true, y_pred, average='weighted', zero_division=0),\n        'recall': recall_score(y_true, y_pred, average='weighted', zero_division=0),\n        'f1': f1_score(y_true, y_pred, average='weighted', zero_division=0),\n        'cm': confusion_matrix(y_true, y_pred)\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.490532Z","iopub.execute_input":"2025-02-08T17:47:55.490795Z","iopub.status.idle":"2025-02-08T17:47:55.507625Z","shell.execute_reply.started":"2025-02-08T17:47:55.490767Z","shell.execute_reply":"2025-02-08T17:47:55.506812Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Main Training Loop\n# -------------------","metadata":{}},{"cell_type":"code","source":"# from tensorflow.keras.applications import ResNet50\n# from tensorflow.keras.layers import Conv2D, UpSampling2D, concatenate, GlobalAveragePooling2D, Dense\n# from tensorflow.keras.models import Model\n# from tensorflow.keras.callbacks import EarlyStopping","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.510643Z","iopub.execute_input":"2025-02-08T17:47:55.510845Z","iopub.status.idle":"2025-02-08T17:47:55.526802Z","shell.execute_reply.started":"2025-02-08T17:47:55.510827Z","shell.execute_reply":"2025-02-08T17:47:55.525969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras.callbacks import EarlyStopping\nfrom sklearn.model_selection import train_test_split\n\n# Initialize models for each series\nseries_models = {\n    'Sagittal T1': build_resnet50(),\n    'Axial T2': build_resnet50(),\n    'Sagittal T2/STIR': build_resnet50()\n}\n\nclass_names = ['normal_mild', 'moderate', 'severe']\nresults = {}\n\nfor series_name, model in series_models.items():\n    # Filter data for the current series and valid severity levels\n    series_df = final_merged_df[\n        (final_merged_df['series_description'] == series_name) &\n        (final_merged_df['severity'].isin(class_names))\n    ].copy()\n\n    if series_df.empty:\n        print(f\"Skipping {series_name} - no valid data.\")\n        continue\n\n    # Split data into train (70%), temp (30%)\n    train_df, temp_df = train_test_split(\n        series_df,\n        test_size=0.3,\n        stratify=series_df['severity'],\n        random_state=42\n    )\n\n    # Split temp into validation (10%) and test (20%)\n    val_df, test_df = train_test_split(\n        temp_df,\n        test_size=0.6667,  # 20%/(10%+20%) = 2/3\n        stratify=temp_df['severity'],\n        random_state=42\n    )\n\n    # Display class distribution\n    print(f\"\\nClass distribution for {series_name}:\")\n    print(\"Train:\", train_df['severity'].value_counts())\n    print(\"Validation:\", val_df['severity'].value_counts())\n    print(\"Test:\", test_df['severity'].value_counts())\n\n    # Create TensorFlow datasets\n    train_ds = create_dataset(train_df, batch_size=64, is_test=False)\n    val_ds = create_dataset(val_df, batch_size=64, is_test=False)\n    test_ds = create_dataset(test_df, batch_size=64, is_test=False)\n    # Train the model\n    history = train_model(\n        model,\n        train_ds,\n        val_ds,\n        series_name,\n    )\n\n    # Evaluate the model on TEST set\n    result = evaluate_model(model, test_ds)\n    results[series_name] = result\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T17:47:55.527923Z","iopub.execute_input":"2025-02-08T17:47:55.528184Z","iopub.status.idle":"2025-02-08T18:00:55.774114Z","shell.execute_reply.started":"2025-02-08T17:47:55.528164Z","shell.execute_reply":"2025-02-08T18:00:55.773394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n    # Plot confusion matrix\n    plot_confusion_matrix(result['cm'], class_names, f'{series_name} Confusion Matrix')\n\n    # Print evaluation metrics\n    print(f\"\\nMetrics for {series_name}:\")\n    print(f\"Precision: {result['precision']:.4f}\")\n    print(f\"Recall: {result['recall']:.4f}\")\n    print(f\"F1-Score: {result['f1']:.4f}\")\n\n    # Plot training history\n    plt.figure(figsize=(12, 5))\n    \n    # Plot Loss\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['loss'], label='Train Loss')\n    plt.plot(history.history['val_loss'], label='Val Loss')\n    plt.title(f'{series_name} Loss')\n    plt.legend()\n    \n    # Plot Accuracy\n    plt.subplot(1, 2, 2)\n    plt.plot(history.history['accuracy'], label='Train Acc')\n    plt.plot(history.history['val_accuracy'], label='Val Acc')\n    plt.title(f'{series_name} Accuracy')\n    plt.legend()\n    \n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:00:55.775202Z","iopub.execute_input":"2025-02-08T18:00:55.775508Z","iopub.status.idle":"2025-02-08T18:00:56.215892Z","shell.execute_reply.started":"2025-02-08T18:00:55.775483Z","shell.execute_reply":"2025-02-08T18:00:56.214970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras.callbacks import EarlyStopping\nfrom sklearn.model_selection import train_test_split\n\ndef check_image_paths(df, image_col='image_path'):\n    invalid_paths = []\n    for path in df[image_col]:\n        if not os.path.isfile(path):\n            invalid_paths.append(path)\n    return invalid_paths\n\ndef filter_valid_image_paths(df, image_col='image_path'):\n    valid_df = df[df[image_col].apply(lambda x: os.path.isfile(x))]\n    return valid_df\n\ndef build_resnet50_fpn(input_shape=(224, 224, 3), num_classes=3):\n    base_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)\n    \n    C2 = base_model.get_layer('conv2_block3_out').output\n    C3 = base_model.get_layer('conv3_block4_out').output\n    C4 = base_model.get_layer('conv4_block6_out').output\n    C5 = base_model.get_layer('conv5_block3_out').output\n    \n    P5 = Conv2D(512, (1, 1), name='fpn_c5p5')(C5)\n    P4 = Conv2D(512, (1, 1), name='fpn_c4p4')(C4)\n    P4 = UpSampling2D(size=(2, 2), name='fpn_p5upsampled')(P5) + P4\n    \n    P3 = Conv2D(512, (1, 1), name='fpn_c3p3')(C3)\n    P3 = UpSampling2D(size=(2, 2), name='fpn_p4upsampled')(P4) + P3\n    \n    P2 = Conv2D(512, (1, 1), name='fpn_c2p2')(C2)\n    P2 = UpSampling2D(size=(2, 2), name='fpn_p3upsampled')(P3) + P2\n    \n    P2 = Conv2D(512, (3, 3), padding='same', name='fpn_p2')(P2)\n    P3 = Conv2D(512, (3, 3), padding='same', name='fpn_p3')(P3)\n    P4 = Conv2D(512, (3, 3), padding='same', name='fpn_p4')(P4)\n    P5 = Conv2D(512, (3, 3), padding='same', name='fpn_p5')(P5)\n    \n    pooled = [GlobalAveragePooling2D()(p) for p in [P2, P3, P4, P5]]\n    merged = concatenate(pooled, axis=-1)\n    \n    merged = BatchNormalization()(merged)\n    merged = Dropout(0.5)(merged)\n    \n    outputs = Dense(num_classes, activation='softmax')(merged)\n    \n    model = Model(inputs=base_model.input, outputs=outputs)\n    \n    return model\n\n# افزایش داده‌ها\ndatagen = ImageDataGenerator(\n    rotation_range=20,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    shear_range=0.2,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    fill_mode='nearest'\n)\n\n# مدل‌ها برای هر سری داده\nseries_models = {\n    'Sagittal T1': build_resnet50_fpn(),\n    'Axial T2': build_resnet50_fpn(),\n    'Sagittal T2/STIR': build_resnet50_fpn()\n}\n\nclass_names = ['normal_mild', 'moderate', 'severe']\nresults = {}\n\n# آموزش مدل برای هر سری داده\nfor series_name, model in series_models.items():\n    series_df = final_merged_df[\n        (final_merged_df['series_description'] == series_name) &\n        (final_merged_df['severity'].isin(class_names))\n    ].copy()\n\n    if series_df.empty:\n        print(f\"Skipping {series_name} - no valid data.\")\n        continue\n\n    # بررسی مسیرهای نامعتبر\n    invalid_paths = check_image_paths(series_df)\n    if invalid_paths:\n        print(f\"Found {len(invalid_paths)} invalid image paths in {series_name}.\")\n        print(invalid_paths[:10])  # نمایش 10 مسیر نامعتبر اول\n        series_df = filter_valid_image_paths(series_df)\n\n    if series_df.empty:\n        print(f\"Skipping {series_name} - no valid image paths after filtering.\")\n        continue\n\n    # تقسیم داده‌ها به آموزش (70%) و موقت (30%)\n    train_df, temp_df = train_test_split(\n        series_df,\n        test_size=0.3,\n        stratify=series_df['severity'],\n        random_state=42\n    )\n\n    # تقسیم موقت به اعتبارسنجی (10%) و تست (20%)\n    val_df, test_df = train_test_split(\n        temp_df,\n        test_size=0.6667,\n        stratify=temp_df['severity'],\n        random_state=42\n    )\n\n    # نمایش توزیع کلاس‌ها\n    print(f\"\\nClass distribution for {series_name}:\")\n    print(\"Train:\", train_df['severity'].value_counts())\n    print(\"Validation:\", val_df['severity'].value_counts())\n    print(\"Test:\", test_df['severity'].value_counts())\n\n    # بررسی خالی نبودن DataFrame‌ها\n    if train_df.empty or val_df.empty or test_df.empty:\n        print(f\"Skipping {series_name} - one of the datasets is empty.\")\n        continue\n\n    # ایجاد مجموعه‌های داده TensorFlow با افزایش داده\n    train_ds = datagen.flow_from_dataframe(\n        dataframe=train_df,\n        x_col='image_path',\n        y_col='severity',\n        target_size=(224, 224),\n        batch_size=64,\n        class_mode='categorical'\n    )\n    \n    val_ds = datagen.flow_from_dataframe(\n        dataframe=val_df,\n        x_col='image_path',\n        y_col='severity',\n        target_size=(224, 224),\n        batch_size=64,\n        class_mode='categorical'\n    )\n    \n    test_ds = datagen.flow_from_dataframe(\n        dataframe=test_df,\n        x_col='image_path',\n        y_col='severity',\n        target_size=(224, 224),\n        batch_size=64,\n        class_mode='categorical'\n    )\n\n    # کامپایل مدل\n    model.compile(optimizer=Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy'])\n\n    # Callback‌ها\n    callbacks = [\n        EarlyStopping(patience=10, restore_best_weights=True),\n        ReduceLROnPlateau(factor=0.1, patience=5)\n    ]\n\n    # آموزش مدل\n    try:\n        history = model.fit(\n            train_ds,\n            validation_data=val_ds,\n            epochs=50,\n            callbacks=callbacks\n        )\n    except ValueError as e:\n        print(f\"Error during model training: {e}\")\n        continue\n\n    # ارزیابی مدل بر روی مجموعه تست\n    result = model.evaluate(test_ds)\n    results[series_name] = result\n\n    # نمایش معیارهای ارزیابی\n    print(f\"\\nMetrics for {series_name}:\")\n    print(f\"Loss: {result[0]:.4f}\")\n    print(f\"Accuracy: {result[1]:.4f}\")\n\n    # ترسیم تاریخچه آموزش\n    plt.figure(figsize=(12, 5))\n    \n    # ترسیم Loss\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['loss'], label='Train Loss')\n    plt.plot(history.history['val_loss'], label='Val Loss')\n    plt.title(f'{series_name} Loss')\n    plt.legend()\n    \n    # ترسیم دقت\n    plt.subplot(1, 2, 2)\n    plt.plot(history.history['accuracy'], label='Train Acc')\n    plt.plot(history.history['val_accuracy'], label='Val Acc')\n    plt.title(f'{series_name} Accuracy')\n    plt.legend()\n    \n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:00:56.216908Z","iopub.execute_input":"2025-02-08T18:00:56.217250Z","iopub.status.idle":"2025-02-08T18:01:15.570023Z","shell.execute_reply.started":"2025-02-08T18:00:56.217217Z","shell.execute_reply":"2025-02-08T18:01:15.569130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from keras.applications import ResNet50\n# from keras.layers import Conv2D, UpSampling2D, concatenate, GlobalAveragePooling2D, Dense, BatchNormalization, Dropout\n# from keras.models import Model\n# from keras.callbacks import EarlyStopping, ReduceLROnPlateau\n# from keras.optimizers import Adam\n# from sklearn.model_selection import train_test_split\n# from tensorflow.keras.preprocessing.image import ImageDataGenerator  # تغییر این خط\n\n# def build_resnet50_fpn(input_shape=(224, 224, 3), num_classes=3):\n#     # Load ResNet50 backbone\n#     base_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)\n    \n#     # Get feature maps from different levels of ResNet50\n#     C2 = base_model.get_layer('conv2_block3_out').output\n#     C3 = base_model.get_layer('conv3_block4_out').output\n#     C4 = base_model.get_layer('conv4_block6_out').output\n#     C5 = base_model.get_layer('conv5_block3_out').output\n    \n#     # FPN Top-down pathway and lateral connections\n#     P5 = Conv2D(512, (1, 1), name='fpn_c5p5')(C5)\n#     P4 = Conv2D(512, (1, 1), name='fpn_c4p4')(C4)\n#     P4 = UpSampling2D(size=(2, 2), name='fpn_p5upsampled')(P5) + P4\n    \n#     P3 = Conv2D(512, (1, 1), name='fpn_c3p3')(C3)\n#     P3 = UpSampling2D(size=(2, 2), name='fpn_p4upsampled')(P4) + P3\n    \n#     P2 = Conv2D(512, (1, 1), name='fpn_c2p2')(C2)\n#     P2 = UpSampling2D(size=(2, 2), name='fpn_p3upsampled')(P3) + P2\n    \n#     # Final convolution layers for each level\n#     P2 = Conv2D(512, (3, 3), padding='same', name='fpn_p2')(P2)\n#     P3 = Conv2D(512, (3, 3), padding='same', name='fpn_p3')(P3)\n#     P4 = Conv2D(512, (3, 3), padding='same', name='fpn_p4')(P4)\n#     P5 = Conv2D(512, (3, 3), padding='same', name='fpn_p5')(P5)\n    \n#     # Global Average Pooling and classification head\n#     pooled = [GlobalAveragePooling2D()(p) for p in [P2, P3, P4, P5]]\n#     merged = concatenate(pooled, axis=-1)\n    \n#     # Add BatchNormalization and Dropout\n#     merged = BatchNormalization()(merged)\n#     merged = Dropout(0.5)(merged)\n    \n#     # Output layer\n#     outputs = Dense(num_classes, activation='softmax')(merged)\n    \n#     # Build the model\n#     model = Model(inputs=base_model.input, outputs=outputs)\n    \n#     return model\n\n# # Data Augmentation\n# datagen = ImageDataGenerator(\n#     rotation_range=20,\n#     width_shift_range=0.2,\n#     height_shift_range=0.2,\n#     shear_range=0.2,\n#     zoom_range=0.2,\n#     horizontal_flip=True,\n#     fill_mode='nearest'\n# )\n\n# # Initialize models for each series with FPN\n# series_models = {\n#     'Sagittal T1': build_resnet50_fpn(),\n#     'Axial T2': build_resnet50_fpn(),\n#     'Sagittal T2/STIR': build_resnet50_fpn()\n# }\n\n# class_names = ['normal_mild', 'moderate', 'severe']\n# results = {}\n\n# for series_name, model in series_models.items():\n#     # Filter data for the current series and valid severity levels\n#     series_df = final_merged_df[\n#         (final_merged_df['series_description'] == series_name) &\n#         (final_merged_df['severity'].isin(class_names))\n#     ].copy()\n\n#     if series_df.empty:\n#         print(f\"Skipping {series_name} - no valid data.\")\n#         continue\n\n#     # Split data into train (70%), temp (30%)\n#     train_df, temp_df = train_test_split(\n#         series_df,\n#         test_size=0.3,\n#         stratify=series_df['severity'],\n#         random_state=42\n#     )\n\n#     # Split temp into validation (10%) and test (20%)\n#     val_df, test_df = train_test_split(\n#         temp_df,\n#         test_size=0.6667,  # 20%/(10%+20%) = 2/3\n#         stratify=temp_df['severity'],\n#         random_state=42\n#     )\n\n#     # Display class distribution\n#     print(f\"\\nClass distribution for {series_name}:\")\n#     print(\"Train:\", train_df['severity'].value_counts())\n#     print(\"Validation:\", val_df['severity'].value_counts())\n#     print(\"Test:\", test_df['severity'].value_counts())\n\n#     # Create TensorFlow datasets with data augmentation\n#     train_ds = datagen.flow_from_dataframe(\n#         dataframe=train_df,\n#         x_col='image_path',  # Replace with your image column name\n#         y_col='severity',\n#         target_size=(224, 224),\n#         batch_size=64,\n#         class_mode='categorical'\n#     )\n    \n#     val_ds = datagen.flow_from_dataframe(\n#         dataframe=val_df,\n#         x_col='image_path',  # Replace with your image column name\n#         y_col='severity',\n#         target_size=(224, 224),\n#         batch_size=64,\n#         class_mode='categorical'\n#     )\n    \n#     test_ds = datagen.flow_from_dataframe(\n#         dataframe=test_df,\n#         x_col='image_path',  # Replace with your image column name\n#         y_col='severity',\n#         target_size=(224, 224),\n#         batch_size=64,\n#         class_mode='categorical'\n#     )\n\n#     # Compile the model\n#     model.compile(optimizer=Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy'])\n\n#     # Callbacks\n#     callbacks = [\n#         EarlyStopping(patience=10, restore_best_weights=True),\n#         ReduceLROnPlateau(factor=0.1, patience=5)\n#     ]\n\n#     # Train the model\n#     history = model.fit(\n#         train_ds,\n#         validation_data=val_ds,\n#         epochs=50,\n#         callbacks=callbacks\n#     )\n\n#     # Evaluate the model on TEST set\n#     result = model.evaluate(test_ds)\n#     results[series_name] = result\n\n#     # Print evaluation metrics\n#     print(f\"\\nMetrics for {series_name}:\")\n#     print(f\"Loss: {result[0]:.4f}\")\n#     print(f\"Accuracy: {result[1]:.4f}\")\n\n#     # Plot training history\n#     plt.figure(figsize=(12, 5))\n    \n#     # Plot Loss\n#     plt.subplot(1, 2, 1)\n#     plt.plot(history.history['loss'], label='Train Loss')\n#     plt.plot(history.history['val_loss'], label='Val Loss')\n#     plt.title(f'{series_name} Loss')\n#     plt.legend()\n    \n#     # Plot Accuracy\n#     plt.subplot(1, 2, 2)\n#     plt.plot(history.history['accuracy'], label='Train Acc')\n#     plt.plot(history.history['val_accuracy'], label='Val Acc')\n#     plt.title(f'{series_name} Accuracy')\n#     plt.legend()\n    \n#     plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:01:15.571115Z","iopub.execute_input":"2025-02-08T18:01:15.571343Z","iopub.status.idle":"2025-02-08T18:01:15.576781Z","shell.execute_reply.started":"2025-02-08T18:01:15.571321Z","shell.execute_reply":"2025-02-08T18:01:15.575904Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from keras.callbacks import EarlyStopping\n# from sklearn.model_selection import train_test_split\n\n# # Initialize models for each series\n# series_models = {\n#     'Sagittal T1': build_resnet50(),\n#     'Axial T2': build_resnet50(),\n#     'Sagittal T2/STIR': build_resnet50()\n# }\n\n# class_names = ['normal_mild', 'moderate', 'severe']\n# results = {}\n\n# for series_name, model in series_models.items():\n#     # Filter data for the current series and valid severity levels\n#     series_df = final_merged_df[\n#         (final_merged_df['series_description'] == series_name) &\n#         (final_merged_df['severity'].isin(class_names))\n#     ].copy()\n\n#     if series_df.empty:\n#         print(f\"Skipping {series_name} - no valid data.\")\n#         continue\n\n#     # Split data into train (70%), temp (30%)\n#     train_df, temp_df = train_test_split(\n#         series_df,\n#         test_size=0.3,\n#         stratify=series_df['severity'],\n#         random_state=42\n#     )\n\n#     # Split temp into validation (10%) and test (20%)\n#     val_df, test_df = train_test_split(\n#         temp_df,\n#         test_size=0.6667,  # 20%/(10%+20%) = 2/3\n#         stratify=temp_df['severity'],\n#         random_state=42\n#     )\n\n#     # Display class distribution\n#     print(f\"\\nClass distribution for {series_name}:\")\n#     print(\"Train:\", train_df['severity'].value_counts())\n#     print(\"Validation:\", val_df['severity'].value_counts())\n#     print(\"Test:\", test_df['severity'].value_counts())\n\n#     # Create TensorFlow datasets\n#     train_ds = create_dataset(train_df, batch_size=64, is_test=False)\n#     val_ds = create_dataset(val_df, batch_size=64, is_test=False)\n#     test_ds = create_dataset(test_df, batch_size=64, is_test=False)\n#     # Train the model\n#     history = train_model(\n#         model,\n#         train_ds,\n#         val_ds,\n#         series_name,\n#     )\n\n#     # Evaluate the model on TEST set\n#     result = evaluate_model(model, test_ds)\n#     results[series_name] = result\n\n#     # Plot confusion matrix\n#     plot_confusion_matrix(result['cm'], class_names, f'{series_name} Confusion Matrix')\n\n#     # Print evaluation metrics\n#     print(f\"\\nMetrics for {series_name}:\")\n#     print(f\"Precision: {result['precision']:.4f}\")\n#     print(f\"Recall: {result['recall']:.4f}\")\n#     print(f\"F1-Score: {result['f1']:.4f}\")\n\n#     # Plot training history\n#     plt.figure(figsize=(12, 5))\n    \n#     # Plot Loss\n#     plt.subplot(1, 2, 1)\n#     plt.plot(history.history['loss'], label='Train Loss')\n#     plt.plot(history.history['val_loss'], label='Val Loss')\n#     plt.title(f'{series_name} Loss')\n#     plt.legend()\n    \n#     # Plot Accuracy\n#     plt.subplot(1, 2, 2)\n#     plt.plot(history.history['accuracy'], label='Train Acc')\n#     plt.plot(history.history['val_accuracy'], label='Val Acc')\n#     plt.title(f'{series_name} Accuracy')\n#     plt.legend()\n    \n#     plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:01:15.577775Z","iopub.execute_input":"2025-02-08T18:01:15.578093Z","iopub.status.idle":"2025-02-08T18:01:15.599814Z","shell.execute_reply.started":"2025-02-08T18:01:15.578062Z","shell.execute_reply":"2025-02-08T18:01:15.598979Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport tensorflow as tf\n\ndef predict_test_and_generate_submission(series_models, expanded_test_desc, class_names):\n    \"\"\"\n    Generate predictions on the test set and create a submission file.\n    Fixed to handle series names with special characters and FutureWarnings.\n    \"\"\"\n    # Use a list to collect prediction DataFrames\n    dfs = []\n\n    for series_name, model in series_models.items():\n        try:\n            # Split the series_name by '/'\n            parts = series_name.split('/')\n            \n            if len(parts) == 2:\n                # Construct path: best_<first_part>/<second_part>.keras\n                weight_dir = f'best_{parts[0]}'\n                weight_filename = f\"{parts[1]}.keras\"\n                weight_path = os.path.join(weight_dir, weight_filename)\n            else:\n                # Construct path: best_<series_name>.keras\n                weight_path = f'best_{series_name}.keras'\n            \n            # Ensure the weight_path is correct\n            if not os.path.exists(weight_path):\n                raise FileNotFoundError(f\"Weight file not found: {weight_path}\")\n\n            # Load model weights\n            model.load_weights(weight_path)\n            print(f\"Loaded weights for '{series_name}' from '{weight_path}'\")\n\n            # Filter test data for current series\n            series_test = expanded_test_desc[expanded_test_desc['series_description'] == series_name]\n\n            if series_test.empty:\n                print(f\"No test data for '{series_name}'. Skipping.\")\n                continue\n\n            # Create test dataset and predict\n            test_ds = create_dataset(series_test, batch_size=64, is_test=True)\n            preds = model.predict(test_ds, verbose=1)\n\n            # Handle model outputs\n            if preds.ndim == 2 and preds.shape[1] > 1:\n                if not np.allclose(preds.sum(axis=1), 1):\n                    probs = tf.nn.softmax(preds, axis=1).numpy()\n                else:\n                    probs = preds\n            elif preds.ndim == 1:\n                # If model outputs a single probability, convert to multi-class\n                probs = np.vstack([1 - preds, preds, np.zeros_like(preds)]).T\n            else:\n                raise ValueError(f\"Unexpected prediction shape: {preds.shape}\")\n\n            # Ensure probs has exactly three columns\n            if probs.shape[1] != 3:\n                raise ValueError(f\"Expected 3 classes, but got {probs.shape[1]} for '{series_name}'\")\n\n            # Create temporary DataFrame\n            temp_df = series_test[['row_id']].copy()\n            temp_df[['normal_mild', 'moderate', 'severe']] = probs\n\n            # Ensure all probability columns are numeric\n            temp_df[['normal_mild', 'moderate', 'severe']] = temp_df[['normal_mild', 'moderate', 'severe']].apply(pd.to_numeric, errors='coerce')\n\n            # Check for any NaNs introduced by non-numeric values\n            if temp_df[['normal_mild', 'moderate', 'severe']].isnull().values.any():\n                raise ValueError(f\"Non-numeric prediction probabilities encountered for '{series_name}'\")\n\n            # Append to list\n            dfs.append(temp_df)\n            print(f\"Added predictions for '{series_name}'\")\n\n        except Exception as e:\n            print(f\"Error in '{series_name}': {str(e)}\")\n            continue\n\n    # Combine all predictions\n    submission = pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame(columns=['row_id', 'normal_mild', 'moderate', 'severe'])\n\n    # Handle missing rows\n    all_row_ids = expanded_test_desc['row_id'].unique()\n    submission_row_ids = submission['row_id'].unique()\n    missing_ids = set(all_row_ids) - set(submission_row_ids)\n    \n    if missing_ids:\n        print(f\"Adding default probabilities for {len(missing_ids)} missing rows\")\n        missing_df = pd.DataFrame({\n            'row_id': list(missing_ids),\n            'normal_mild': 1/3,\n            'moderate': 1/3,\n            'severe': 1/3\n        })\n        submission = pd.concat([submission, missing_df], ignore_index=True)\n\n    # Aggregate and normalize\n    numeric_cols = ['normal_mild', 'moderate', 'severe']\n    submission = submission.groupby('row_id')[numeric_cols].mean().reset_index()\n    \n    # Normalize the probabilities to ensure they sum to 1\n    submission[numeric_cols] = (\n        submission[numeric_cols]\n        .div(submission[numeric_cols].sum(axis=1), axis=0)\n        .fillna(1/3)\n        .round(3)\n    )\n\n    # Verify that all probability columns are numeric using numpy\n    if not np.isreal(submission[numeric_cols].values).all():\n        raise ValueError(\"Non-numeric values detected in the probability columns after aggregation.\")\n\n    # Save submission\n    submission.to_csv('/kaggle/working/submission.csv', index=False)\n    print(\"Submission file 'submission.csv' created successfully\")\n\npredict_test_and_generate_submission(series_models, expanded_test_desc, class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:01:15.600614Z","iopub.execute_input":"2025-02-08T18:01:15.600839Z","iopub.status.idle":"2025-02-08T18:01:25.495861Z","shell.execute_reply.started":"2025-02-08T18:01:15.600819Z","shell.execute_reply":"2025-02-08T18:01:25.494883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\ndf = pd.read_csv(\"/kaggle/working/submission.csv\")\ndf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:01:25.496778Z","iopub.execute_input":"2025-02-08T18:01:25.497015Z","iopub.status.idle":"2025-02-08T18:01:25.509647Z","shell.execute_reply.started":"2025-02-08T18:01:25.496982Z","shell.execute_reply":"2025-02-08T18:01:25.508835Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# df = pd.read_csv(\"/kaggle/working/submission.csv\")\n# df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-08T18:01:25.510403Z","iopub.execute_input":"2025-02-08T18:01:25.510623Z","iopub.status.idle":"2025-02-08T18:01:25.522636Z","shell.execute_reply.started":"2025-02-08T18:01:25.510602Z","shell.execute_reply":"2025-02-08T18:01:25.521956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}