{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":70203,"databundleVersionId":8068726},{"sourceType":"datasetVersion","sourceId":8209908,"datasetId":4865209,"databundleVersionId":8334538},{"sourceType":"modelInstanceVersion","sourceId":32637,"databundleVersionId":8261530,"modelInstanceId":26649,"modelId":37756},{"sourceType":"modelInstanceVersion","sourceId":516989,"databundleVersionId":13353982,"modelInstanceId":404337,"modelId":319},{"sourceType":"kernelVersion","sourceId":314394777}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Day 2: Extract Embeddings\nThis notebook loops through the focus species training data and extracts embeddings using Perch and BirdNET.","metadata":{}},{"cell_type":"code","source":"!pip install -q --upgrade \"tensorflow[and-cuda]>=2.16.1\" birdnetlib pyarrow resampy\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:01:45.239429Z","iopub.execute_input":"2026-04-25T17:01:45.239689Z","iopub.status.idle":"2026-04-25T17:03:04.866584Z","shell.execute_reply.started":"2026-04-25T17:01:45.239667Z","shell.execute_reply":"2026-04-25T17:03:04.865894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nprint(\"Num GPUs Available: \", len(tf.config.list_physical_devices('GPU')))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:03:04.868258Z","iopub.execute_input":"2026-04-25T17:03:04.868703Z","iopub.status.idle":"2026-04-25T17:03:25.616137Z","shell.execute_reply.started":"2026-04-25T17:03:04.868662Z","shell.execute_reply":"2026-04-25T17:03:25.615263Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile extract_embeddings.py\nimport librosa\nimport numpy as np\nimport tensorflow as tf\nfrom birdnetlib import Recording\nfrom birdnetlib.analyzer import Analyzer\n\nSR = 32000\nCHUNK_SEC = 5.0\n\ndef load_audio(path: str, sr: int = SR) -> np.ndarray:\n    try:\n        y, _ = librosa.load(path, sr=sr, mono=True)\n        return y\n    except Exception as e:\n        print(f\"Error loading {path}: {e}\")\n        return np.array([])\n\ndef chunks(y: np.ndarray, sec: float = CHUNK_SEC, sr: int = SR, overlap: float = 0.0):\n    step = int(sec * sr * (1 - overlap))\n    win = int(sec * sr)\n    for i in range(0, max(1, len(y) - win + 1), step):\n        c = y[i : i + win]\n        if len(c) < win:\n            c = np.pad(c, (0, win - len(c)))\n        yield c\n\n# -----------------\n# Offline BirdNET\n# -----------------\ntry:\n    BIRDNET_MODEL_PATH = '/kaggle/input/birdnet-analyzer-2-4-0/BirdNET_GLOBAL_6K_V2.4_Model_FP32.tflite'\n    analyzer = Analyzer()\nexcept Exception as e:\n    analyzer = None\n\ndef birdnet_predict(audio_path: str, lat=10.5, lon=76.5):\n    if not analyzer: return []\n    # Removed the invalid 'week' and 'overlap' arguments\n    rec = Recording(analyzer, audio_path, lat=lat, lon=lon, min_conf=0.0)\n    try:\n        rec.analyze()\n        return rec.detections\n    except:\n        return []\n\n\n\n# -----------------\n# Offline Perch v2\n# -----------------\ntry:\n    from pathlib import Path\n    pb_path = next(Path(\"/kaggle/input\").rglob(\"saved_model.pb\"))\n    PERCH_MODEL_PATH = str(pb_path.parent)\n    _raw_model = tf.saved_model.load(PERCH_MODEL_PATH)\n    perch = _raw_model.signatures[\"serving_default\"]\nexcept Exception as e:\n    perch = None\n\ndef perch_embed(audio_5s_32k: np.ndarray) -> np.ndarray:\n    if not perch: return np.zeros(1536)\n    x = tf.constant(audio_5s_32k[None, :], dtype=tf.float32)\n    # Use the new TensorFlow Signature wrapper\n    out = perch(inputs=x)\n    return out[\"embedding\"].numpy()[0]\n\ndef perch_logits(audio_5s_32k: np.ndarray) -> np.ndarray:\n    if not perch: return np.zeros(10000)\n    x = tf.constant(audio_5s_32k[None, :], dtype=tf.float32)\n    out = perch(inputs=x)\n    return out[\"label\"].numpy()[0]\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:03:25.617250Z","iopub.execute_input":"2026-04-25T17:03:25.617836Z","iopub.status.idle":"2026-04-25T17:03:25.623891Z","shell.execute_reply.started":"2026-04-25T17:03:25.617809Z","shell.execute_reply":"2026-04-25T17:03:25.623080Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nimport extract_embeddings\nfrom tqdm.auto import tqdm\n\nINPUT_DIR = Path('/kaggle/input')\nDATA_OUT = Path('./data')\nCACHE_OUT = Path('./cache/embeddings')\nCACHE_OUT.mkdir(parents=True, exist_ok=True)\n\n# Dynamically find the train_audio folder based on where the metadata is!\nTRAIN_CSV = next(INPUT_DIR.rglob('train_metadata.csv'))\nAUDIO_DIR = TRAIN_CSV.parent / 'train_audio'\nprint(f\"Audio directory set to: {AUDIO_DIR}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:03:25.625851Z","iopub.execute_input":"2026-04-25T17:03:25.626207Z","iopub.status.idle":"2026-04-25T17:03:34.553315Z","shell.execute_reply.started":"2026-04-25T17:03:25.626182Z","shell.execute_reply":"2026-04-25T17:03:34.552625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load Focus Species and Metadata\ntry:\n    species_csv_path = next(Path('/kaggle/input').rglob('species_focus.csv'))\n    focus_df = pd.read_csv(species_csv_path)\n    focus_species = focus_df['species'].tolist()\n    print(f\"Found species_focus.csv! Loaded {len(focus_species)} species.\")\nexcept StopIteration:\n    print(\"WARNING: Could not find species_focus.csv. Make sure you attached the Day 1 notebook output.\")\n    focus_species = []\n\ntry:\n    TRAIN_CSV = next(INPUT_DIR.rglob('train_metadata.csv'))\n    train_df = pd.read_csv(TRAIN_CSV)\nexcept StopIteration:\n    train_df = pd.DataFrame()\n\nif len(focus_species) > 0 and not train_df.empty:\n    train_df = train_df[train_df['primary_label'].isin(focus_species)]\n    sampled_df = train_df.groupby('primary_label').head(100).reset_index(drop=True)\n    print(f\"Processing {len(sampled_df)} total audio files across {len(focus_species)} species...\")\nelse:\n    sampled_df = pd.DataFrame()\n    print(\"Skipping loop because metadata is missing.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:03:34.554354Z","iopub.execute_input":"2026-04-25T17:03:34.554774Z","iopub.status.idle":"2026-04-25T17:03:34.754243Z","shell.execute_reply.started":"2026-04-25T17:03:34.554705Z","shell.execute_reply":"2026-04-25T17:03:34.753298Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Extraction Loop\nresults_perch = []\nresults_birdnet = []\n\nfor idx, row in tqdm(sampled_df.iterrows(), total=len(sampled_df)):\n    file_path = AUDIO_DIR / row['filename']\n    if not file_path.exists():\n        continue\n        \n    # Load audio\n    y = extract_embeddings.load_audio(str(file_path))\n    if len(y) == 0: continue\n        \n    # Process first 5-second chunk for now (for zero-shot evaluation)\n    chunk = list(extract_embeddings.chunks(y))[0]\n    \n    # Perch\n    emb = extract_embeddings.perch_embed(chunk)\n    results_perch.append({\n        'file_id': row['filename'],\n        'species_code': row['primary_label'],\n        'emb': emb\n    })\n    \n    # BirdNET (returns list of dicts with confidences, we'll just save the raw detections for now)\n    detections = extract_embeddings.birdnet_predict(str(file_path))\n    results_birdnet.append({\n        'file_id': row['filename'],\n        'species_code': row['primary_label'],\n        'detections': str(detections)\n    })\n    \n    # Save checkpoints periodically to avoid memory issues and kernel death loss\n    if (idx + 1) % 500 == 0 or (idx + 1) == len(sampled_df):\n        pd.DataFrame(results_perch).to_parquet(CACHE_OUT / f'perch_train_{idx}.parquet')\n        pd.DataFrame(results_birdnet).to_parquet(CACHE_OUT / f'birdnet_train_{idx}.parquet')\n        results_perch, results_birdnet = [], []\n\nprint(\"Extraction complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T17:03:34.755414Z","iopub.execute_input":"2026-04-25T17:03:34.755895Z","execution_failed":"2026-04-25T17:05:56.170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}