{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Define ","metadata":{}},{"cell_type":"code","source":"import os                                                                                                                                                                                                                                                                                                \n\n# Define the output directory\nOUTPUT_DIR = '/kaggle/working/'\n\n# Create the output directory if it doesn't exist\nos.makedirs(OUTPUT_DIR, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:49:30.167797Z","iopub.execute_input":"2025-05-28T08:49:30.168086Z","iopub.status.idle":"2025-05-28T08:49:30.172452Z","shell.execute_reply.started":"2025-05-28T08:49:30.168064Z","shell.execute_reply":"2025-05-28T08:49:30.171820Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Optimized Audio Preprocessing for Kaggle\nProcess One Folder for optimize resources","metadata":{}},{"cell_type":"code","source":"import os\nimport librosa\nimport numpy as np\n\n# Choose a single folder to process\nfolder_name = \"bkmtou1\"  # Change this to the folder you want to process\n\nfolder_path = f'/kaggle/input/birdclef-2025/train_audio/{folder_name}/'\naudio_files = [f for f in os.listdir(folder_path) if f.endswith('.ogg')]\n\n# Process each audio file in the folder\nfor audio_file in audio_files:\n    audio_path = os.path.join(folder_path, audio_file)\n    audio, sr = librosa.load(audio_path, sr=32000)  # Use 32 kHz sampling rate as required\n    # Add your audio processing logic here\n    print(f\"Processed: {audio_file}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:49:30.185907Z","iopub.execute_input":"2025-05-28T08:49:30.186334Z","iopub.status.idle":"2025-05-28T08:49:45.193251Z","shell.execute_reply.started":"2025-05-28T08:49:30.186316Z","shell.execute_reply":"2025-05-28T08:49:45.192467Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Then memory management","metadata":{}},{"cell_type":"code","source":"import gc\n\n# After processing each file or batch\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:49:45.194564Z","iopub.execute_input":"2025-05-28T08:49:45.195077Z","iopub.status.idle":"2025-05-28T08:49:45.483211Z","shell.execute_reply.started":"2025-05-28T08:49:45.195056Z","shell.execute_reply":"2025-05-28T08:49:45.482395Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset Preparation:\nFast & Lightweight Mel Spectrogram Preprocessing (One Folder Only)","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport torch\nimport torchaudio\nimport torchaudio.transforms as T\nimport matplotlib.pyplot as plt\n\n# Define constants\nTRAIN_AUDIO_PATH = \"/kaggle/input/birdclef-2025/train_audio\"\nOUTPUT_DIR = \"/kaggle/working/\"\nTARGET_FOLDER = \"blbwre1\"  # you can change this to any subfolder\nMAX_FILES = 10\nSAMPLE_RATE = 16000\nDURATION_SEC = 5\nAUDIO_LENGTH = SAMPLE_RATE * DURATION_SEC\n\n# Ensure output directory exists\nos.makedirs(OUTPUT_DIR, exist_ok=True)\n\n# Mel spectrogram transform\nmel_transform = T.MelSpectrogram(\n    sample_rate=SAMPLE_RATE,\n    n_fft=1024,\n    hop_length=256,\n    n_mels=128,\n)\n\n# Utility to load and trim/pad audio\ndef load_trim_pad(path):\n    waveform, sr = torchaudio.load(path)\n    if sr != SAMPLE_RATE:\n        waveform = torchaudio.functional.resample(waveform, sr, SAMPLE_RATE)\n    waveform = waveform[:, :AUDIO_LENGTH]\n    pad_len = AUDIO_LENGTH - waveform.shape[1]\n    if pad_len > 0:\n        waveform = torch.nn.functional.pad(waveform, (0, pad_len))\n    return waveform\n\n# Get a small sample of files\nfolder_path = os.path.join(TRAIN_AUDIO_PATH, TARGET_FOLDER)\nall_files = [f for f in os.listdir(folder_path) if f.endswith(\".ogg\")]\nsample_files = random.sample(all_files, min(MAX_FILES, len(all_files)))\n\n# Process and save mel spectrograms\nfor filename in sample_files:\n    filepath = os.path.join(folder_path, filename)\n    waveform = load_trim_pad(filepath)\n    mel_spec = mel_transform(waveform)\n\n    # Optional: Save spectrograms for quick visualization or caching\n    save_path = os.path.join(OUTPUT_DIR, filename.replace(\".ogg\", \".pt\"))\n    torch.save(mel_spec, save_path)\n\n    print(f\"Processed {filename} → {save_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:49:45.484193Z","iopub.execute_input":"2025-05-28T08:49:45.484483Z","iopub.status.idle":"2025-05-28T08:49:46.958060Z","shell.execute_reply.started":"2025-05-28T08:49:45.484460Z","shell.execute_reply":"2025-05-28T08:49:46.957336Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Fast Random Audio Augmentations (50% chance each)","metadata":{}},{"cell_type":"code","source":"import torchaudio.functional as F\n\ndef apply_random_augmentations(waveform, sample_rate):\n    if random.random() < 0.5:\n        rate = random.uniform(0.8, 1.2)  # time stretch (0.8x to 1.2x)\n        waveform = F.phase_vocoder(\n            T.Spectrogram()(waveform), rate, torch.zeros(1)  # just placeholder phase\n        ) if waveform.size(1) > 1 else waveform  # avoid if 1-frame\n    if random.random() < 0.5:\n        n_steps = random.uniform(-2, 2)  # pitch shift in semitones\n        waveform = F.pitch_shift(waveform, sample_rate, n_steps)\n    if random.random() < 0.5:\n        gain_db = random.uniform(-6, 6)  # volume adjustment\n        waveform = waveform * (10 ** (gain_db / 20))\n    return waveform","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:09.608121Z","iopub.execute_input":"2025-05-28T08:50:09.608881Z","iopub.status.idle":"2025-05-28T08:50:09.614624Z","shell.execute_reply.started":"2025-05-28T08:50:09.608847Z","shell.execute_reply":"2025-05-28T08:50:09.614034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"waveform = load_trim_pad(filepath)\nwaveform = apply_random_augmentations(waveform, SAMPLE_RATE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:53:55.292300Z","iopub.execute_input":"2025-05-28T08:53:55.292571Z","iopub.status.idle":"2025-05-28T08:53:57.422899Z","shell.execute_reply.started":"2025-05-28T08:53:55.292552Z","shell.execute_reply":"2025-05-28T08:53:57.422243Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## Visualization Function: Before vs After Augmentation","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef plot_augmented_spectrogram(waveform, sample_rate):\n    # Original mel spectrogram\n    mel_original = T.MelSpectrogram(sample_rate=sample_rate, n_mels=128)(waveform)\n\n    # Apply augmentations\n    augmented_waveform = apply_random_augmentations(waveform.clone(), sample_rate)\n    mel_augmented = T.MelSpectrogram(sample_rate=sample_rate, n_mels=128)(augmented_waveform)\n\n    # Convert to log scale for better visibility\n    mel_original_db = torchaudio.transforms.AmplitudeToDB()(mel_original)\n    mel_augmented_db = torchaudio.transforms.AmplitudeToDB()(mel_augmented)\n\n    # Plot side-by-side\n    fig, axs = plt.subplots(1, 2, figsize=(12, 4))\n    axs[0].imshow(mel_original_db.squeeze().numpy(), origin=\"lower\", aspect=\"auto\", cmap=\"viridis\")\n    axs[0].set_title(\"Original Mel Spectrogram\")\n\n    axs[1].imshow(mel_augmented_db.squeeze().numpy(), origin=\"lower\", aspect=\"auto\", cmap=\"magma\")\n    axs[1].set_title(\"Augmented Mel Spectrogram\")\n\n    for ax in axs:\n        ax.set_xlabel(\"Time\")\n        ax.set_ylabel(\"Mel bins\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:54:10.455071Z","iopub.execute_input":"2025-05-28T08:54:10.455347Z","iopub.status.idle":"2025-05-28T08:54:10.461409Z","shell.execute_reply.started":"2025-05-28T08:54:10.455326Z","shell.execute_reply":"2025-05-28T08:54:10.460676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Preparing the data for machine learning models ","metadata":{}},{"cell_type":"code","source":"import librosa\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# Path to the audio file\naudio_path = '/kaggle/input/birdclef-2025/train_audio/bkmtou1/XC383275.ogg'\n\n# Load the audio file\ny, sr = librosa.load(audio_path, sr=32000)\n\n# Extract features (e.g., Mel-frequency cepstral coefficients)\nmfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)\n\n# Print the shape of the MFCC features\nprint(f\"MFCC shape: {mfcc.shape}\")\n\n# Save the MFCC features to a NumPy file in the output directory\noutput_path = os.path.join(OUTPUT_DIR, 'mfcc_features.npy')\nnp.save(output_path, mfcc)\nprint(f\"MFCC features saved to {output_path}\")\n\n# Optionally, you can visualize the MFCCs\nplt.figure(figsize=(10, 4))\nlibrosa.display.specshow(mfcc, x_axis='time', y_axis='mel', sr=sr, fmax=8000)\nplt.colorbar(format='%+2.0f dB')\nplt.title('MFCC')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:54:16.098047Z","iopub.execute_input":"2025-05-28T08:54:16.098800Z","iopub.status.idle":"2025-05-28T08:54:16.399489Z","shell.execute_reply.started":"2025-05-28T08:54:16.098769Z","shell.execute_reply":"2025-05-28T08:54:16.398791Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## Model Definition: LightCRNN (Convolution + GRU)","metadata":{}},{"cell_type":"code","source":"#  Fixed Model Definition\nimport torch\nimport torch.nn as nn\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n# Ensure you have the model class defined earlier\nclass BirdCLEFCRNN(nn.Module):\n    def __init__(self, num_classes=30):\n        super(BirdCLEFCRNN, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(1, 16, kernel_size=3, padding=1),\n            nn.BatchNorm2d(16),\n            nn.ReLU(),\n            nn.MaxPool2d(2),  # (B, 16, H/2, W/2)\n\n            nn.Conv2d(16, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(2),  # (B, 32, H/4, W/4)\n\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.AdaptiveMaxPool2d((64, 32))  # Force output to (64, 32)\n        )\n        self.gru = nn.GRU(input_size=64 * 64, hidden_size=64, batch_first=True, bidirectional=True)\n        self.fc = nn.Linear(128, num_classes)\n\n    def forward(self, x):\n        x = self.conv(x)  # (B, 64, 64, 32)\n        B, C, H, W = x.size()\n        x = x.permute(0, 3, 1, 2).contiguous()  # (B, W=32, C=64, H=64)\n        x = x.view(B, W, C * H)  # (B, 32, 4096)\n        x, _ = self.gru(x)\n        x = self.fc(x[:, -1, :])  # Last time step\n        return x\n\n# Instantiate the model\nmodel = BirdCLEFCRNN(num_classes=30).to(device)\n\n# Check if model is correctly defined\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:35.117058Z","iopub.execute_input":"2025-05-28T08:50:35.117672Z","iopub.status.idle":"2025-05-28T08:50:35.142786Z","shell.execute_reply.started":"2025-05-28T08:50:35.117645Z","shell.execute_reply":"2025-05-28T08:50:35.142048Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dummy Training Loop Setup","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import DataLoader, TensorDataset\nimport torch.optim as optim\n\n# Dummy parameters\nbatch_size = 8\nnum_classes = 30\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# Model\nmodel = BirdCLEFCRNN(num_classes=num_classes).to(device)\n\n# Dummy data\ndummy_x = torch.randn(batch_size, 1, 128, 256).to(device)\ndummy_y = torch.randint(0, num_classes, (batch_size,)).to(device)\n\n# DataLoader\ndataset = TensorDataset(dummy_x, dummy_y)\nloader = DataLoader(dataset, batch_size=batch_size)\n\n# Optimizer + Loss\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss()\n\n# Dummy training loop (1 epoch)\nmodel.train()\nfor batch_x, batch_y in loader:\n    optimizer.zero_grad()\n    outputs = model(batch_x)\n    loss = criterion(outputs, batch_y)\n    loss.backward()\n    optimizer.step()\n    print(f\"Dummy loss: {loss.item():.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:40.202552Z","iopub.execute_input":"2025-05-28T08:50:40.203105Z","iopub.status.idle":"2025-05-28T08:50:40.241489Z","shell.execute_reply.started":"2025-05-28T08:50:40.203082Z","shell.execute_reply":"2025-05-28T08:50:40.240756Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Define BirdCLEFCRNN","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n# Ensure you have the model class defined earlier\nclass BirdCLEFCRNN(nn.Module):\n    def __init__(self, num_classes=30):\n        super(BirdCLEFCRNN, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(1, 16, kernel_size=3, padding=1),\n            nn.BatchNorm2d(16),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n            nn.Conv2d(16, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.AdaptiveMaxPool2d((64, 32)),\n        )\n        self.gru = nn.GRU(32, 64, batch_first=True, bidirectional=True)\n        self.fc = nn.Linear(128, num_classes)\n    \n    def forward(self, x):\n        x = self.conv(x)\n        x = x.view(x.size(0), -1)  # Flattening the tensor for GRU\n        x, _ = self.gru(x)\n        x = self.fc(x[:, -1, :])  # Get the output from the last GRU step\n        return x\n\n# Instantiate the model\nmodel = BirdCLEFCRNN(num_classes=30).to(device)\n\n# Check if model is correctly defined\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:44.442935Z","iopub.execute_input":"2025-05-28T08:50:44.443659Z","iopub.status.idle":"2025-05-28T08:50:44.456153Z","shell.execute_reply.started":"2025-05-28T08:50:44.443632Z","shell.execute_reply":"2025-05-28T08:50:44.455564Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Dataset Class","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset\nimport os\n\nclass BirdCLEFMelDataset(Dataset):\n    def __init__(self, data_dir, label_map, transform=None):\n        self.data_dir = data_dir\n        self.file_list = [f for f in os.listdir(data_dir) if f.endswith(\".pt\")]\n        self.label_map = label_map  # Dict[str -> int] or list index for multi-label\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.file_list)\n\n    def __getitem__(self, idx):\n        fname = self.file_list[idx]\n        tensor = torch.load(os.path.join(self.data_dir, fname))  # shape: [1, H, W]\n        \n        # Dummy label: Replace with actual label extraction logic if needed\n        species_id = fname.split(\".\")[0].split(\"_\")[0]  # e.g., XC123456 → XC123456\n        label = self.label_map.get(species_id, 0)  # default to 0 or multi-hot\n        \n        if self.transform:\n            tensor = self.transform(tensor)\n\n        return tensor, torch.tensor(label, dtype=torch.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:48.578795Z","iopub.execute_input":"2025-05-28T08:50:48.579070Z","iopub.status.idle":"2025-05-28T08:50:48.585310Z","shell.execute_reply.started":"2025-05-28T08:50:48.579047Z","shell.execute_reply":"2025-05-28T08:50:48.584479Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dummy Label Map for Testing","metadata":{}},{"cell_type":"code","source":"# Dummy label map for testing (assuming 30 classes)\nimport random\nlabel_map = {fname.split(\".\")[0]: random.randint(0, 29) for fname in os.listdir(\"/kaggle/working\") if fname.endswith(\".pt\")}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:52.830732Z","iopub.execute_input":"2025-05-28T08:50:52.831004Z","iopub.status.idle":"2025-05-28T08:50:52.835367Z","shell.execute_reply.started":"2025-05-28T08:50:52.830984Z","shell.execute_reply":"2025-05-28T08:50:52.834803Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create DataLoader","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntrain_dataset = BirdCLEFMelDataset(\"/kaggle/working\", label_map)\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:50:56.061063Z","iopub.execute_input":"2025-05-28T08:50:56.061655Z","iopub.status.idle":"2025-05-28T08:50:56.066349Z","shell.execute_reply.started":"2025-05-28T08:50:56.061629Z","shell.execute_reply":"2025-05-28T08:50:56.065664Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## Test Loading a Batch","metadata":{}},{"cell_type":"code","source":"for batch in train_loader:\n    x, y = batch\n    print(\"Input:\", x.shape)  # Expected: [B, 1, H, W]\n    print(\"Labels:\", y.shape)  # Expected: [B] or [B, num_classes]\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:51:00.766712Z","iopub.execute_input":"2025-05-28T08:51:00.767362Z","iopub.status.idle":"2025-05-28T08:51:00.862079Z","shell.execute_reply.started":"2025-05-28T08:51:00.767337Z","shell.execute_reply":"2025-05-28T08:51:00.861170Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Before running the prediction loop","metadata":{}},{"cell_type":"code","source":"%%time\nimport torchaudio\nimport torchaudio.transforms as T\n\ndef preprocess_audio_file(file_path, sample_rate=32000, n_mels=128, mel_len=313):\n    waveform, sr = torchaudio.load(file_path)\n\n    # Resample if needed\n    if sr != sample_rate:\n        resampler = T.Resample(orig_freq=sr, new_freq=sample_rate)\n        waveform = resampler(waveform)\n\n    # Mono\n    if waveform.shape[0] > 1:\n        waveform = waveform.mean(dim=0, keepdim=True)\n\n    # Mel spectrogram\n    mel_spec = T.MelSpectrogram(\n        sample_rate=sample_rate,\n        n_fft=1024,\n        hop_length=512,\n        n_mels=n_mels\n    )(waveform)\n\n    # Convert to log scale (dB)\n    log_mel = T.AmplitudeToDB()(mel_spec)\n\n    # Ensure fixed length (padding or truncating to fixed time frames)\n    if log_mel.shape[-1] < mel_len:\n        pad_amount = mel_len - log_mel.shape[-1]\n        log_mel = torch.nn.functional.pad(log_mel, (0, pad_amount))\n    else:\n        log_mel = log_mel[:, :, :mel_len]\n\n    return log_mel  # shape: [1, n_mels, mel_len]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:51:04.947007Z","iopub.execute_input":"2025-05-28T08:51:04.947301Z","iopub.status.idle":"2025-05-28T08:51:04.954468Z","shell.execute_reply.started":"2025-05-28T08:51:04.947276Z","shell.execute_reply":"2025-05-28T08:51:04.953595Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Evaluation\n\nThe sample data and the evaluation process, including the missing part where the results are printed and saved to a CSV file.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom sklearn.metrics import f1_score\n\n# Sample data for ground truth and predictions\nground_truth_data = {\n    'recording_id': ['0001', '0002', '0003', '0004', '0005'],\n    'x': [10.5, 15.4, 12.3, 18.2, 14.1],\n    'y': [20.3, 25.6, 22.1, 28.4, 24.3],\n    'z': [30.7, 35.8, 32.9, 38.6, 34.5]\n}\n\npredictions_data = {\n    'recording_id': ['0001', '0002', '0003', '0004', '0005'],\n    'x': [10.6, 15.5, 12.4, 18.3, 14.2],\n    'y': [20.4, 25.7, 22.2, 28.5, 24.4],\n    'z': [30.8, 35.9, 33.0, 38.7, 34.6]\n}\n\n# Convert dictionaries to DataFrames\nground_truth = pd.DataFrame(ground_truth_data)\npredictions = pd.DataFrame(predictions_data)\n\n# Ensure both DataFrames are aligned by recording_id\nground_truth = ground_truth.set_index('recording_id')\npredictions = predictions.set_index('recording_id')\n\n# Function to calculate Euclidean distance\ndef euclidean_distance(row):\n    y_true = ground_truth.loc[row.name]\n    y_pred = row\n    d = np.sqrt((y_true['x'] - y_pred['x'])**2 + \n                (y_true['y'] - y_pred['y'])**2 + \n                (y_true['z'] - y_pred['z'])**2)\n    return d\n\n# Calculate Euclidean distance for each prediction\npredictions['distance'] = predictions.apply(euclidean_distance, axis=1)\n\n# Determine True Positives (TP) and False Negatives (FN)\nthreshold = 10.0  # Angstroms\npredictions['is_TP'] = predictions['distance'] <= threshold\n\n# Calculate TP, FP, and FN\nTP = predictions['is_TP'].sum()\nFP = predictions.shape[0] - TP  # False Positives are all predictions that are not True Positives\nFN = ground_truth.shape[0] - TP  # False Negatives are all ground truth that are not True Positives\n\n# Calculate Precision and Recall\nprecision = TP / (TP + FP) if (TP + FP) > 0 else 0\nrecall = TP / (TP + FN) if (TP + FN) > 0 else 0\n\n# Calculate F1 Score\nf1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0\n\n# Print results\nprint(f\"True Positives (TP): {TP}\")\nprint(f\"False Positives (FP): {FP}\")\nprint(f\"False Negatives (FN): {FN}\")\nprint(f\"Precision: {precision:.4f}\")\nprint(f\"Recall: {recall:.4f}\")\nprint(f\"F1 Score: {f1:.4f}\")\n\n# Optionally, you can save the results to a CSV file\nresults = pd.DataFrame({\n    'TP': [TP],\n    'FP': [FP],\n    'FN': [FN],\n    'Precision': [precision],\n    'Recall': [recall],\n    'F1 Score': [f1]\n})\nresults.to_csv('evaluation_results.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:51:09.812321Z","iopub.execute_input":"2025-05-28T08:51:09.812812Z","iopub.status.idle":"2025-05-28T08:51:09.831328Z","shell.execute_reply.started":"2025-05-28T08:51:09.812787Z","shell.execute_reply":"2025-05-28T08:51:09.830481Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize ","metadata":{}},{"cell_type":"markdown","source":"### 1. Data Visualization:","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Plot the distribution of distances\nplt.hist(predictions['distance'], bins=10, alpha=0.7, color='blue')\nplt.axvline(x=threshold, color='red', linestyle='dashed', linewidth=2)\nplt.title('Distribution of Euclidean Distances')\nplt.xlabel('Distance (Angstroms)')\nplt.ylabel('Frequency')\nplt.show()\n\n# Plot true positives and false negatives\nplt.figure(figsize=(10, 8))\nplt.scatter(ground_truth['x'], ground_truth['y'], label='Ground Truth', color='green')\nplt.scatter(predictions.loc[predictions['is_TP'], 'x'], predictions.loc[predictions['is_TP'], 'y'], label='True Positives', color='blue')\nplt.scatter(predictions.loc[~predictions['is_TP'], 'x'], predictions.loc[~predictions['is_TP'], 'y'], label='False Positives', color='red')\nplt.title('True Positives and False Positives')\nplt.xlabel('X')\nplt.ylabel('Y')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:51:15.556905Z","iopub.execute_input":"2025-05-28T08:51:15.557180Z","iopub.status.idle":"2025-05-28T08:51:15.917542Z","shell.execute_reply.started":"2025-05-28T08:51:15.557159Z","shell.execute_reply":"2025-05-28T08:51:15.916821Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2. Cross-Validation:","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import KFold\n\nkfold = KFold(n_splits=5, shuffle=True, random_state=42)\nf1_scores = []\n\nfor train_index, val_index in kfold.split(ground_truth):\n    train_gt = ground_truth.iloc[train_index]\n    val_gt = ground_truth.iloc[val_index]\n    train_pred = predictions.iloc[train_index]\n    val_pred = predictions.iloc[val_index]\n\n    # Calculate distances for validation set\n    val_pred['distance'] = val_pred.apply(euclidean_distance, axis=1)\n    val_pred['is_TP'] = val_pred['distance'] <= threshold\n\n    TP = val_pred['is_TP'].sum()\n    FP = val_pred.shape[0] - TP\n    FN = val_gt.shape[0] - TP\n\n    precision = TP / (TP + FP) if (TP + FP) > 0 else 0\n    recall = TP / (TP + FN) if (TP + FN) > 0 else 0\n    f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0\n\n    f1_scores.append(f1)\n\nprint(f\"Average F1 Score: {np.mean(f1_scores):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T08:51:21.697270Z","iopub.execute_input":"2025-05-28T08:51:21.697580Z","iopub.status.idle":"2025-05-28T08:51:21.714455Z","shell.execute_reply.started":"2025-05-28T08:51:21.697553Z","shell.execute_reply":"2025-05-28T08:51:21.713749Z"}},"outputs":[],"execution_count":null}]}