{"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":"none","dataSources":[{"sourceId":91249,"databundleVersionId":11294684,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<div style=\"display: flex; justify-content: space-between; align-items: flex-start;\">\n    <div style=\"text-align: left;\">\n        <p style=\"color:#FFD700; font-size: 15px; font-weight: bold; margin-bottom: 1px; text-align: left;\">Published on  March 7, 2025</p>\n        <h4 style=\"color:#4B0082; font-weight: bold; text-align: left; margin-top: 6px;\">Author: Jocelyn C. Dumlao</h4>\n        <p style=\"font-size: 17px; line-height: 1.7; color: #333; text-align: center; margin-top: 20px;\"></p>\n        <a href=\"https://www.linkedin.com/in/jocelyn-dumlao-168921a8/\" target=\"_blank\" style=\"display: inline-block; background-color: #003f88; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">LinkedIn</a>\n        <a href=\"https://github.com/jcdumlao14\" target=\"_blank\" style=\"display: inline-block; background-color: transparent; color: #059c99; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px; border: 2px solid #007bff;\">GitHub</a>\n        <a href=\"https://www.youtube.com/@CogniCraftedMinds\" target=\"_blank\" style=\"display: inline-block; background-color: #ff0054; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">YouTube</a>\n        <a href=\"https://www.kaggle.com/jocelyndumlao\" target=\"_blank\" style=\"display: inline-block; background-color: #3a86ff; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">Kaggle</a>\n    </div>\n</div>","metadata":{}},{"cell_type":"markdown","source":"<center>\n  <img src=\"https://www.kaggle.com/competitions/91249/images/header\" alt=\"image\">\n</center>\n","metadata":{}},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Introduction</p>\n\n- This project aims to develop a system for automatically locating bacterial flagellar motors within 3D tomogram images. The problem is approached using deep learning, specifically a 3D Convolutional Neural Network (CNN), to learn patterns in the tomograms and predict the coordinates of the motors. Successful localization has significant implications for understanding bacterial motility and developing targeted therapies.\n\n- **Goal:** Locate bacterial flagellar motors within 3D tomogram images and submit the predicted coordinates.\n\n- **Approach:** Use a 3D Convolutional Neural Network (CNN) trained on tomogram images and their corresponding motor locations to predict the motor coordinates in unseen tomograms.","metadata":{}},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Setup and Imports</p>\n\n- **Import Libraries:** Imports necessary libraries like `os`, `numpy`, `pandas`, `PIL (Image`), `torch`, `sklearn`, `tqdm`, and `matplotlib`.\n\n- **Device Selection:** Determines if a CUDA-enabled GPU is available and sets the device accordingly (`cuda` or `cpu`).","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport cv2  # added for image processing\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n# Define the device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:30:22.622803Z","iopub.execute_input":"2025-03-07T23:30:22.623182Z","iopub.status.idle":"2025-03-07T23:30:27.958949Z","shell.execute_reply.started":"2025-03-07T23:30:22.623108Z","shell.execute_reply":"2025-03-07T23:30:27.957737Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Installed Dependencies","metadata":{}},{"cell_type":"code","source":"!pip download -d ./packages ultralytics\n!tar cfvz archive.tar.gz ./packages","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-03-07T23:30:32.372724Z","iopub.execute_input":"2025-03-07T23:30:32.373314Z","iopub.status.idle":"2025-03-07T23:34:35.235498Z","shell.execute_reply.started":"2025-03-07T23:30:32.373281Z","shell.execute_reply":"2025-03-07T23:34:35.233337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!tar xfvz archive.tar.gz\n!pip install --no-index --find-links=./packages ultralytics\n!rm -rf ./packages ","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-03-07T23:34:45.850546Z","iopub.execute_input":"2025-03-07T23:34:45.850966Z","iopub.status.idle":"2025-03-07T23:35:27.487572Z","shell.execute_reply.started":"2025-03-07T23:34:45.850930Z","shell.execute_reply":"2025-03-07T23:35:27.485663Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Data Loading and Preprocessing</p>\n\n- **Define Paths:** Specifies paths to training data, testing data, training labels, and submission file.\n\n- **Load Training Labels:** Reads the `train_labels.csv` file into a Pandas DataFrame, removing duplicate entries.\n\n- `load_tomogram(tomo_dir, resize, fixed_depth)` **Function**:\n\n   - Loads a tomogram (stack of images) from a directory.\n\n   - Resizes each image to the specified `resize` dimensions.\n\n   - Pads or truncates the image stack to have a fixed depth of `fixed_depth`.\n\n   - Normalizes pixel values to the range [0, 1].\n\n- `TomogramDataset(Dataset)` **Class:**\n\n   - A custom PyTorch Dataset class to handle the tomogram data.\n\n   - `__init__`: Initializes the dataset with data directory, labels DataFrame, resize dimensions, fixed depth, and a flag to indicate if it's a test dataset.\n\n   - `__len__`: Returns the number of tomograms in the dataset.\n\n   - `__getitem__`: Loads a tomogram and its corresponding labels (if available) based on the index. Returns the tomogram as a PyTorch tensor, labels as a PyTorch tensor, and the tomo_id as a string.\n","metadata":{}},{"cell_type":"code","source":"# --- 1. Data Loading and Preprocessing ---\n\n# Define data paths\ndata_path = \"/kaggle/input/byu-locating-bacterial-flagellar-motors-2025/\"\ntest_dir = os.path.join(data_path, \"test\")\ntrain_dir = os.path.join(data_path, \"train\")  \ntrain_labels_path = os.path.join(data_path, \"train_labels.csv\")  \nsubmission_path = os.path.join(data_path, \"submission.csv\") \noutput_path = 'submission.csv'  # Saves it in the /kaggle/working/ directory\n\n\n# Load training labels\ntrain_labels_df = pd.read_csv(train_labels_path).drop_duplicates(subset='tomo_id', keep='first').set_index('tomo_id')\nprint(\"Train Labels DataFrame:\")\ntrain_labels_df.head().style.background_gradient(cmap='plasma')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:24.042236Z","iopub.execute_input":"2025-03-07T23:36:24.042743Z","iopub.status.idle":"2025-03-07T23:36:24.259491Z","shell.execute_reply.started":"2025-03-07T23:36:24.042706Z","shell.execute_reply":"2025-03-07T23:36:24.258301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to load a tomogram (stack of images)\ndef load_tomogram(tomo_dir, resize=(64, 64), fixed_depth=32):\n    slices = []\n    try:\n        slice_files = sorted([f for f in os.listdir(tomo_dir) if f.endswith('.jpg')])\n    except FileNotFoundError:\n        print(f\"Error: Tomo directory not found: {tomo_dir}\")\n        return None  # Handle the error and return None\n\n    if len(slice_files) < fixed_depth:\n        padding_needed = fixed_depth - len(slice_files)\n        empty_img = Image.new('L', resize, color=0)\n        slices.extend([np.array(empty_img)] * padding_needed)\n    else:\n        slice_files = slice_files[:fixed_depth]  # truncate if more than fixed_depth\n\n    for slice_file in slice_files:\n        img = Image.open(os.path.join(tomo_dir, slice_file)).convert('L')\n        img = img.resize(resize, Image.Resampling.LANCZOS)\n        slices.append(np.array(img))\n\n    return np.stack(slices).astype(np.float32) / 255.0\n\n\n# Custom Dataset class\nclass TomogramDataset(Dataset):\n    def __init__(self, data_dir, labels_df, resize=(64, 64), fixed_depth=32, is_test=False):\n        self.data_dir = data_dir\n        self.labels_df = labels_df\n\n        # Correctly handle the test dataset case where labels may not be available.\n        if is_test:\n            # Get a list of tomo_ids from the directory names\n            self.tomogram_ids = [f for f in os.listdir(data_dir) if os.path.isdir(os.path.join(data_dir, f))]\n        else:\n            self.tomogram_ids = list(labels_df.index)\n\n        self.resize = resize\n        self.fixed_depth = fixed_depth\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.tomogram_ids)\n\n    def __getitem__(self, idx):\n        tomo_id = self.tomogram_ids[idx]\n        tomogram_path = os.path.join(self.data_dir, tomo_id)\n\n        tomogram = load_tomogram(tomogram_path, resize=self.resize, fixed_depth=self.fixed_depth)\n\n        if tomogram is None:\n            print(f\"Warning: Skipping tomo_id {tomo_id} due to loading error.\")\n            # Handle the error case by returning None or a zero-filled array.\n            # Returning None will require changes to the DataLoader's collate_fn,\n            # but returning a zero-filled array allows the training loop to continue.\n            tomogram = np.zeros((1, self.fixed_depth, self.resize[0], self.resize[1]), dtype=np.float32)\n            labels = np.array([-1.0, -1.0, -1.0], dtype=np.float32)  # Default labels\n            return torch.tensor(tomogram).unsqueeze(0), torch.tensor(labels), tomo_id  # ***unsqueeze here\n\n        tomogram = torch.tensor(tomogram).unsqueeze(0)  # *** Move unsqueeze here. Ensures consistent shape\n        if not self.is_test:\n            labels = self.labels_df.loc[tomo_id, ['Motor axis 0', 'Motor axis 1', 'Motor axis 2']].values.astype(np.float32)\n        else:\n            labels = np.array([-1.0, -1.0, -1.0], dtype=np.float32)\n\n        return tomogram, torch.tensor(labels), tomo_id\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:29.600925Z","iopub.execute_input":"2025-03-07T23:36:29.601527Z","iopub.status.idle":"2025-03-07T23:36:29.614790Z","shell.execute_reply.started":"2025-03-07T23:36:29.601493Z","shell.execute_reply":"2025-03-07T23:36:29.613296Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Model Definition</p>\n- `Simple3DCNN(nn.Module)` Class:\n\n   - Defines a simple 3D Convolutional Neural Network (CNN) model.\n\n   - Includes 3D convolutional layers, ReLU activation functions, and max-pooling layers.\n\n   - A fully connected layer maps the features to 3 output coordinates.\n","metadata":{}},{"cell_type":"code","source":"# --- 2. Model Definition ---\n\nclass Simple3DCNN(nn.Module):\n    def __init__(self, fixed_depth=32):\n        super(Simple3DCNN, self).__init__()\n        self.conv1 = nn.Conv3d(1, 16, kernel_size=3, padding=1)\n        self.relu1 = nn.ReLU()\n        self.pool1 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=2)  # Added depth dimension to pooling\n        self.conv2 = nn.Conv3d(16, 32, kernel_size=3, padding=1)\n        self.relu2 = nn.ReLU()\n        self.pool2 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=2)  # Added depth dimension to pooling\n        self.flatten = nn.Flatten()\n\n        # Calculate the size of the flattened layer dynamically\n        self.flattened_size = 32 * (fixed_depth // 4) * (64 // 4) * (64 // 4)\n        self.fc1 = nn.Linear(self.flattened_size, 128)\n        self.relu3 = nn.ReLU()\n        self.fc2 = nn.Linear(128, 3)\n\n    def forward(self, x):\n        x = self.pool1(self.relu1(self.conv1(x)))\n        x = self.pool2(self.relu2(self.conv2(x)))\n        x = self.flatten(x)\n        x = self.relu3(self.fc1(x))\n        return self.fc2(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:33.897060Z","iopub.execute_input":"2025-03-07T23:36:33.897544Z","iopub.status.idle":"2025-03-07T23:36:33.906088Z","shell.execute_reply.started":"2025-03-07T23:36:33.897500Z","shell.execute_reply":"2025-03-07T23:36:33.904950Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Training</p>\n\n- `train_model(model, train_loader, criterion, optimizer, num_epochs, display_images)` **Function:**\n\n   - Iterates through the training data for a specified number of epochs.\n\n   - For each batch, it moves data to the device, calculates loss, performs backpropagation, and updates model parameters.\n\n   - Includes an option to display sample images from the first batch of each epoch.\n","metadata":{}},{"cell_type":"code","source":"# --- 3. Training ---\n\ndef train_model(model, train_loader, criterion, optimizer, num_epochs=10, display_images=False):\n    model.train()\n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        for batch_idx, (tomogram, labels, _) in enumerate(tqdm(train_loader, desc=f\"Epoch {epoch + 1}/{num_epochs} - Training\")):\n            # Skip batches where tomogram is None (due to loading errors)\n            if tomogram is None:\n                print(\"Skipping batch due to None tomogram.\")\n                continue\n\n            tomogram, labels = tomogram.to(device), labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(tomogram)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item()\n\n            # Display sample tomogram images\n            if display_images and batch_idx == 0:  # Show images from the first batch\n                fig, axes = plt.subplots(1, 5, figsize=(15, 5))  # Display 5 slices\n                sample_tomo = tomogram[0].cpu().squeeze().numpy()  # Convert first tomogram in batch to NumPy, remove the channel dimension\n                depth = sample_tomo.shape[0]  # Get depth of 3D volume\n                slice_indices = np.linspace(0, depth - 1, 5, dtype=int)  # Select 5 slices\n\n                for i, idx in enumerate(slice_indices):\n                    axes[i].imshow(sample_tomo[idx], cmap='gray')  # Show slice\n                    axes[i].set_title(f\"Slice {idx}\")\n                    axes[i].axis('off')\n\n                plt.show()\n\n        print(f\"Epoch {epoch + 1}/{num_epochs}: Train Loss: {running_loss / len(train_loader):.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:38.305096Z","iopub.execute_input":"2025-03-07T23:36:38.305478Z","iopub.status.idle":"2025-03-07T23:36:38.314676Z","shell.execute_reply.started":"2025-03-07T23:36:38.305450Z","shell.execute_reply":"2025-03-07T23:36:38.312925Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Evaluation Metric Implementation</p>\n\n- `calculate_fbeta_score(predictions, ground_truths, threshold, beta)` **Function:**\n\n   - Calculates the F-beta score, precision, and recall for the motor prediction task.\n\n   - A true positive is determined if the Euclidean distance between the prediction and the ground truth is within the specified threshold.\n\n","metadata":{}},{"cell_type":"markdown","source":"<h3>Using a Combination of the <i>F<sub>β</sub>-score</i> and <i>Euclidean Distance</i></h3>\n\n<p>\nLet the ground truth be <b>y</b> and the predicted location be <b>&#x0177;</b>.  \nThe Euclidean distance <b>\\(|y - \\bar{y}|_2\\)</b> determines classification:\n</p>\n\n<ul>\n    <li><b>True Positive (TP):</b> If \\( |y - \\bar{y}|_2 \\leq \\tau \\), the prediction is within the threshold.</li>\n    <li><b>False Negative (FN):</b> If \\( |y - \\bar{y}|_2 > \\tau \\), the prediction is outside the threshold.</li>\n</ul>\n\n<p>where <b>\\( \\tau = 1000 \\)</b> Angstroms.</p>\n\n<h3><b>F<sub>β</sub>-score</b></h3>\n\n<p>\nThe <b>F<sub>β</sub>-score</b> balances precision and recall, placing greater weight on recall when <b>\\( \\beta > 1 \\)</b>  \nand on precision when <b>\\( \\beta < 1 \\)</b>. (In our case, we use <b>\\( \\beta = 2 \\)</b>, thus weighting recall more than precision).  \nIt is defined as:\n</p>\n\n<p>\n\\[\nF_{\\beta} = \\frac{(1 + \\beta^2) \\cdot \\text{precision} \\cdot \\text{recall}}{(\\beta^2 \\cdot \\text{precision}) + \\text{recall}}\n\\]\n</p>\n\n<p>Since precision and recall are given by:</p>\n\n<p>\n\\[\n\\text{Precision} = \\frac{TP}{TP + FP}, \\quad \\text{Recall} = \\frac{TP}{TP + FN}\n\\]\n</p>\n\n<p>The <b>F<sub>β</sub>-score</b> simplifies to:</p>\n\n<p>\n\\[\nF_{\\beta} = \\frac{(1 + \\beta^2) \\cdot TP}{(1 + \\beta^2) \\cdot TP + \\beta^2 \\cdot FN + FP}\n\\]\n</p>\n","metadata":{}},{"cell_type":"code","source":"# --- 4. Evaluation Metric Implementation ---\n\ndef calculate_fbeta_score(predictions, ground_truths, threshold=1000, beta=2):\n    \"\"\"\n    Calculates the F-beta score for the motor prediction task.\n\n    Args:\n        predictions (np.ndarray): Predicted motor locations (N x 3).\n        ground_truths (np.ndarray): Ground truth motor locations (N x 3).\n        threshold (float): Euclidean distance threshold for True Positive.\n        beta (float): Beta parameter for F-beta score.\n\n    Returns:\n        float: F-beta score.\n    \"\"\"\n    tp = 0\n    fp = 0\n    fn = 0\n\n    for i in range(len(ground_truths)):\n        gt = ground_truths[i]\n        pred = predictions[i]\n\n        # Check if motor exists in ground truth\n        motor_exists_gt = not all(gt == -1)\n\n        # Check if motor is predicted\n        motor_predicted = not all(pred == -1)\n\n        if motor_exists_gt and motor_predicted:\n            distance = np.linalg.norm(gt - pred)\n            if distance <= threshold:\n                tp += 1\n            else:\n                fp += 1\n                fn += 1\n        elif motor_exists_gt and not motor_predicted:\n            fn += 1\n        elif not motor_exists_gt and motor_predicted:\n            fp += 1\n\n    # Calculate precision, recall, and F-beta score\n    if tp + fp == 0:\n        precision = 0.0\n    else:\n        precision = tp / (tp + fp)\n\n    if tp + fn == 0:\n        recall = 0.0\n    else:\n        recall = tp / (tp + fn)\n\n    if precision + recall == 0:\n        fbeta = 0.0\n    else:\n        fbeta = (1 + beta**2) * (precision * recall) / ((beta**2 * precision) + recall)\n\n    return fbeta, precision, recall  # Return precision and recall\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:43.536060Z","iopub.execute_input":"2025-03-07T23:36:43.536437Z","iopub.status.idle":"2025-03-07T23:36:43.544740Z","shell.execute_reply.started":"2025-03-07T23:36:43.536408Z","shell.execute_reply":"2025-03-07T23:36:43.543239Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Prediction and Submission</p>\n- `predict(model, test_loader)` **Function:**\n\n   - Sets the model to evaluation mode.\n\n   - Iterates through the test data and makes predictions using the trained model.\n\n   - Stores the predictions in a dictionary, where keys are tomo_ids and values are predicted coordinates.\n\n- `create_submission_file(predictions, submission_path, output_path)` **Function:**\n\n   - Reads the submission file template.\n\n   - Replaces the coordinates in the submission file with the predicted coordinates.\n\n   - Saves the updated submission file.\n\n","metadata":{}},{"cell_type":"code","source":"# --- 5. Prediction and Submission ---\n# Modify predict function\n\ndef predict(model, test_loader, confidence_threshold=0.45, max_detections_per_tomo=1):  # Added params\n    model.eval()\n    predictions = {}\n    with torch.no_grad():\n        for tomogram, _, tomo_ids in tqdm(test_loader, desc=\"Predicting\"):\n            # Check if tomogram is None, skip if so.\n            if tomogram is None:\n                print(f\"Skipping tomo_ids {tomo_ids} due to None tomogram.\")\n                continue\n\n            outputs = model(tomogram.to(device)).cpu().numpy()  # Get predictions\n\n            for i, tomo_id in enumerate(tomo_ids):\n                # Get the prediction for the current tomogram\n                prediction = outputs[i]\n\n                # Filter predictions based on confidence threshold\n                # No confidence scores available with this model, so returning the prediction directly\n                predictions[tomo_id] = prediction\n\n    return predictions\n\n\ndef create_submission_file(predictions, submission_path, output_path='submission.csv'):\n    \"\"\"\n    Creates a submission file using an existing submission CSV as a template.\n\n    Args:\n        predictions (dict): A dictionary where keys are tomo_ids and values are predicted coordinates.\n        submission_path (str): Path to the existing submission CSV file.\n        output_path (str): Path to save the updated submission CSV.\n    \"\"\"\n    try:\n        submission = pd.read_csv(submission_path)\n    except FileNotFoundError:\n        print(f\"Error: Submission file not found at {submission_path}\")\n        # *** IMPORTANT: If the submission file is NOT found, create a dummy one.\n        # *** This is crucial for Kaggle to accept the submission.\n\n        # Create a list of all tomo_ids in the test set\n        test_tomos = sorted([d for d in os.listdir(test_dir) if os.path.isdir(os.path.join(test_dir, d))])\n\n        # Create a DataFrame with tomo_ids and default predictions (-1)\n        submission = pd.DataFrame({'tomo_id': test_tomos, 'Motor axis 0': -1, 'Motor axis 1': -1, 'Motor axis 2': -1})\n        submission[['Motor axis 0', 'Motor axis 1', 'Motor axis 2']] = submission[['Motor axis 0', 'Motor axis 1', 'Motor axis 2']].astype(float) # important: cast to float\n\n        print(\"Created a dummy submission file because the original was not found.\")\n\n\n    # Make sure the necessary columns exist.  If not, add them filled with -1.\n    if 'Motor axis 0' not in submission.columns:\n        submission['Motor axis 0'] = -1.0\n    if 'Motor axis 1' not in submission.columns:\n        submission['Motor axis 1'] = -1.0\n    if 'Motor axis 2' not in submission.columns:\n        submission['Motor axis 2'] = -1.0\n    submission[['Motor axis 0', 'Motor axis 1', 'Motor axis 2']] = submission[['Motor axis 0', 'Motor axis 1', 'Motor axis 2']].astype(float)  # important: cast to float\n\n    # Iterate through predictions and update the submission DataFrame\n    for tomo_id, coords in predictions.items():\n        try:\n            submission.loc[submission['tomo_id'] == tomo_id, ['Motor axis 0', 'Motor axis 1', 'Motor axis 2']] = coords\n        except KeyError:\n            print(f\"Warning: tomo_id {tomo_id} not found in submission file.\")\n\n    # Save the updated submission file\n    submission.to_csv(output_path, index=False)\n    print(f\"Submission file created/updated at {output_path}\")\n\n    # Display the head of the submission file\n    print(\"\\nSubmission file head:\")\n    print(submission.head())  # added to show submission head\n\n    # Force the file to be written to disk\n    import sys\n    sys.stdout.flush()\n    # os.fsync(sys.stdout.fileno())\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:49.555931Z","iopub.execute_input":"2025-03-07T23:36:49.556332Z","iopub.status.idle":"2025-03-07T23:36:49.567992Z","shell.execute_reply.started":"2025-03-07T23:36:49.556300Z","shell.execute_reply":"2025-03-07T23:36:49.566540Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Main Execution</p>\n- **Set Hyperparameters:** Defines batch size, learning rate, number of epochs, resize dimensions, fixed depth and threshold for calculation of F-beta score.\n\n- **Create Datasets and DataLoaders:** Creates instances of the `TomogramDataset` for training and testing data and then creates PyTorch DataLoaders to load the data in batches.\n\n- **Initialize Model, Loss, and Optimizer:** Creates an instance of the `Simple3DCNN` model, defines the Mean Squared Error (MSE) loss function, and initializes the Adam optimizer.\n\n- **Train the Model:** Calls the `train_model` function to train the model.\n\n- **Make Predictions:** Calls the `predict` function to make predictions on the test data.\n\n- **Create Submission File:** Calls the `create_submission_file` function to generate the submission file.\n\n- **Evaluation:**\n\n   - Loads ground truth labels for evaluation.\n\n   - Converts predictions to a NumPy array, aligning with the order of ground truths.\n\n   - Calculates and prints the F-beta score, precision, and recall.\n\n   - Plots the Precision-Recall curve and the F-beta score vs. Threshold.","metadata":{}},{"cell_type":"code","source":"# --- Main Execution ---\n\nif __name__ == '__main__':\n    batch_size = 4\n    learning_rate = 0.001\n    num_epochs = 5\n    fixed_depth = 32\n    resize_dims = (64, 64)\n    threshold = 1000\n    beta = 2\n\n    # Define detection parameters (using values from the YOLO script)\n    CONFIDENCE_THRESHOLD = 0.45\n    MAX_DETECTIONS_PER_TOMO = 1  # Keep track of top N detections per tomogram\n\n    # Create datasets\n    train_dataset = TomogramDataset(train_dir, train_labels_df, resize=resize_dims, fixed_depth=fixed_depth)\n    test_dataset = TomogramDataset(test_dir, train_labels_df, resize=resize_dims, fixed_depth=fixed_depth, is_test=True)\n\n    # Create data loaders\n    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n    # Initialize model, loss, and optimizer\n    model = Simple3DCNN(fixed_depth).to(device)\n    criterion = nn.MSELoss()\n    optimizer = optim.Adam(model.parameters(), lr=learning_rate)\n\n    # Train the model\n    train_model(model, train_loader, criterion, optimizer, num_epochs, display_images=True)\n\n    # Make predictions\n    predictions = predict(model, test_loader, CONFIDENCE_THRESHOLD, MAX_DETECTIONS_PER_TOMO)  # pass threshold and max detections\n\n    # Create submission file\n    create_submission_file(predictions, submission_path)  # pass the path to existing submission CSV\n\n    # --- Evaluation and Plotting ---\n\n    # Load the ground truth labels for evaluation (using training labels for demonstration)\n    ground_truths = train_labels_df[['Motor axis 0', 'Motor axis 1', 'Motor axis 2']].values\n\n    # Convert predictions dictionary to a numpy array, aligning with ground truth order\n    predicted_values = []\n    tomo_ids_in_order = train_labels_df.index.tolist()  # Get tomo_ids in the order they appear in ground_truths\n    for tomo_id in tomo_ids_in_order:\n        predicted_values.append(predictions.get(tomo_id, [-1, -1, -1]))  # Use get() to handle missing predictions\n    predictions_array = np.array(predicted_values)\n\n    # Calculate evaluation metrics\n    fbeta, precision, recall = calculate_fbeta_score(predictions_array, ground_truths, threshold, beta)\n\n    print(f\"F-beta score: {fbeta:.4f}\")\n    print(f\"Precision: {precision:.4f}\")\n    print(f\"Recall: {recall:.4f}\")\n\n    # Plotting Precision-Recall Curve\n    precisions = [precision]  # For a single point\n    recalls = [recall]\n    plt.figure(figsize=(8, 6))\n    plt.plot(recalls, precisions, marker='o', linestyle='-')\n    plt.xlabel(\"Recall\")\n    plt.ylabel(\"Precision\")\n    plt.title(\"Precision-Recall Curve\")\n    plt.xlim([0.0, 1.0])\n    plt.ylim([0.0, 1.0])\n    plt.grid(True)\n    plt.show()\n\n    # Plotting F-beta score vs. Threshold\n\n    thresholds = np.arange(500, 2000, 100)\n    fbeta_scores = []\n    for t in thresholds:\n        fbeta_score, _, _ = calculate_fbeta_score(predictions_array, ground_truths, t, beta)\n        fbeta_scores.append(fbeta_score)\n\n    plt.figure(figsize=(8, 6))\n    plt.plot(thresholds, fbeta_scores, marker='o', linestyle='-')\n    plt.xlabel(\"Threshold\")\n    plt.ylabel(\"F-beta Score\")\n    plt.title(\"F-beta Score vs. Threshold\")\n    plt.grid(True)\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-07T23:36:56.571751Z","iopub.execute_input":"2025-03-07T23:36:56.572158Z","iopub.status.idle":"2025-03-08T00:09:23.499530Z","shell.execute_reply.started":"2025-03-07T23:36:56.572101Z","shell.execute_reply":"2025-03-08T00:09:23.498349Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <p style=\"padding:10px;background-color:#d7e8ef;font-family:newtimeroman;font-size:100%;text-align:center;border-radius:12px;font-weight:300;border: 6px outset #f2102e;\">Conclusion</p>\n\n- This work demonstrates a functional, end-to-end pipeline for predicting flagellar motor locations in tomogram data using a relatively simple 3D CNN. While the presented model provides a baseline, further improvements could be explored via hyperparameter optimization, more complex network architectures, data augmentation, and potentially by incorporating information beyond the raw image data. The results presented here offer a foundation for further research into automated flagellar motor localization.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}