{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59156,"databundleVersionId":13874099,"sourceType":"competition"}],"dockerImageVersionId":31089,"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  September 20, 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/59156/images/header\" alt=\"image\" width=\"50%\">\n</center>","metadata":{}},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Import Libraries</p></div>\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom tqdm.notebook import tqdm  \nfrom sklearn.metrics import f1_score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:19.160081Z","iopub.execute_input":"2025-12-09T01:06:19.160424Z","iopub.status.idle":"2025-12-09T01:06:20.285729Z","shell.execute_reply.started":"2025-12-09T01:06:19.160396Z","shell.execute_reply":"2025-12-09T01:06:20.284673Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Data Loading and Filtering</p></div>\n","metadata":{}},{"cell_type":"code","source":"# Data Loading and Filtering\ntrain_df = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/train.csv')\ntrain_subset = train_df.query(\"~ lab_id.str.startswith('MABe22_')\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:25.798282Z","iopub.execute_input":"2025-12-09T01:06:25.798797Z","iopub.status.idle":"2025-12-09T01:06:25.900641Z","shell.execute_reply.started":"2025-12-09T01:06:25.798771Z","shell.execute_reply":"2025-12-09T01:06:25.899767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def score_class_specific(solution, submission):\n    \"\"\"\n    Calculates F1 score separately for each behavior class and averages them (Macro F1).\n    This matches the leaderboard logic much closer than the binary check.\n    \"\"\"\n    print(\"Calculating Class-Specific Frame-level F1 Score...\")\n    \n    # Get all unique videos\n    video_ids = solution['video_id'].unique()\n    \n    # Store F1 scores per behavior class\n    behavior_scores = {}\n    \n    for vid in tqdm(video_ids, desc=\"Scoring Videos\"):\n        sol_vid = solution[solution['video_id'] == vid]\n        sub_vid = submission[submission['video_id'] == vid]\n        \n        # Get max frame for this video\n        if sub_vid.empty and sol_vid.empty:\n            continue\n        max_frame_sol = sol_vid['stop_frame'].max() if not sol_vid.empty else 0\n        max_frame_sub = sub_vid['stop_frame'].max() if not sub_vid.empty else 0\n        max_frame = max(max_frame_sol, max_frame_sub)\n        \n        # Identify all unique behaviors in this video's solution\n        unique_behaviors = sol_vid['action'].unique()\n        \n        for action in unique_behaviors:\n            # Create boolean arrays for this specific action\n            y_true = np.zeros(max_frame + 1, dtype=int)\n            y_pred = np.zeros(max_frame + 1, dtype=int)\n            \n            # Fill True values\n            sol_action = sol_vid[sol_vid['action'] == action]\n            for row in sol_action.itertuples():\n                y_true[row.start_frame:row.stop_frame] = 1\n                \n            sub_action = sub_vid[sub_vid['action'] == action]\n            for row in sub_action.itertuples():\n                # We perform a clip here to ensure we don't go out of bounds if prediction is longer\n                start = min(row.start_frame, max_frame)\n                stop = min(row.stop_frame, max_frame)\n                y_pred[start:stop] = 1\n            \n            # Calculate F1 for this action in this video\n            score = f1_score(y_true, y_pred, zero_division=0)\n            \n            if action not in behavior_scores:\n                behavior_scores[action] = []\n            behavior_scores[action].append(score)\n            \n    # Calculate Macro Average\n    final_scores = [np.mean(scores) for scores in behavior_scores.values()]\n    macro_f1 = np.mean(final_scores) if final_scores else 0\n    \n    print(f\"Class-Specific F1 Score: {macro_f1:.4f}\")\n    return macro_f1\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:29.331243Z","iopub.execute_input":"2025-12-09T01:06:29.331618Z","iopub.status.idle":"2025-12-09T01:06:29.343005Z","shell.execute_reply.started":"2025-12-09T01:06:29.331581Z","shell.execute_reply":"2025-12-09T01:06:29.341941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Ground Truth Generation</p></div>\n","metadata":{}},{"cell_type":"code","source":"# Ground Truth Generation (Validation Baseline)\n\ndef generate_ground_truth(dataset):\n    ground_truth_list = []\n    # We limit to first 100 for speed in local testing, remove .head(100) for full run\n    for _, row in tqdm(dataset.iterrows(), total=len(dataset), desc=\"Processing Annotations\"):\n        lab_id = row['lab_id']\n        video_id = row['video_id']\n        annotation_file_path = f\"/kaggle/input/MABe-mouse-behavior-detection/train_annotation/{lab_id}/{video_id}.parquet\"\n        try:\n            annotations_df = pd.read_parquet(annotation_file_path)\n            annotations_df['video_id'] = video_id\n            annotations_df['lab_id'] = lab_id\n            annotations_df['behaviors_labeled'] = row['behaviors_labeled']\n            annotations_df['agent_id'] = annotations_df['agent_id'].apply(lambda x: f\"mouse{x}\")\n            annotations_df['target_id'] = annotations_df['target_id'].apply(lambda x: f\"mouse{x}\")\n            ground_truth_list.append(annotations_df)\n        except FileNotFoundError:\n            continue\n    return pd.concat(ground_truth_list, ignore_index=True)\n\n# Generate GT for Validation\nsolution = generate_ground_truth(train_subset)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:36.616296Z","iopub.execute_input":"2025-12-09T01:06:36.616674Z","iopub.status.idle":"2025-12-09T01:06:41.701543Z","shell.execute_reply.started":"2025-12-09T01:06:36.616647Z","shell.execute_reply":"2025-12-09T01:06:41.700758Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Prediction</p></div>","metadata":{}},{"cell_type":"code","source":"# Prediction \ndef create_all_in_predictions(dataset, traintest):\n    \"\"\"\n    Strategy: For every behavior listed in 'behaviors_labeled', \n    predict it for the FULL duration of the video.\n    \"\"\"\n    predictions = []\n\n    for _, row in tqdm(dataset.iterrows(), total=len(dataset), desc=\"Generating Predictions\"):\n        lab_id = row['lab_id']\n        video_id = row['video_id']\n        \n        tracking_file_path = f\"/kaggle/input/MABe-mouse-behavior-detection/{traintest}_tracking/{lab_id}/{video_id}.parquet\"\n        \n        try:\n            # Get video duration\n            tracking_meta = pd.read_parquet(tracking_file_path, columns=['video_frame'])\n            stop_frame_total = tracking_meta['video_frame'].max() + 1\n        except FileNotFoundError:\n            # Fallback if tracking file missing\n            stop_frame_total = 1800 # default fallback\n            # print(f\"Warning: Tracking missing for {video_id}\")\n\n        # Parse behaviors\n        behaviors_labeled_str = row['behaviors_labeled']\n        try:\n            behaviors_labeled = eval(behaviors_labeled_str)\n        except:\n            continue\n            \n        cleaned_behaviors = [b.replace(\"'\", \"\").strip() for b in behaviors_labeled]\n        behavior_tuples = [tuple(b.split(',')) for b in cleaned_behaviors]\n        \n        # Iterate over EVERY behavior tuple found in the metadata\n        for b_tuple in behavior_tuples:\n            if len(b_tuple) == 3:\n                agent, target, action = b_tuple\n                \n                # Predict this action for the WHOLE video [0, stop_frame_total]\n                predictions.append({\n                    'video_id': video_id,\n                    'agent_id': agent.strip(),\n                    'target_id': target.strip(),\n                    'action': action.strip(),\n                    'start_frame': 0,\n                    'stop_frame': stop_frame_total\n                })\n\n    predictions_df = pd.DataFrame(predictions)\n    return predictions_df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:44.698047Z","iopub.execute_input":"2025-12-09T01:06:44.698471Z","iopub.status.idle":"2025-12-09T01:06:44.708736Z","shell.execute_reply.started":"2025-12-09T01:06:44.698448Z","shell.execute_reply":"2025-12-09T01:06:44.707457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate predictions\nsubmission = create_all_in_predictions(train_subset, 'train')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:48.336738Z","iopub.execute_input":"2025-12-09T01:06:48.337099Z","iopub.status.idle":"2025-12-09T01:06:54.032986Z","shell.execute_reply.started":"2025-12-09T01:06:48.337071Z","shell.execute_reply":"2025-12-09T01:06:54.031839Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Evaluation</p></div>","metadata":{}},{"cell_type":"code","source":"# Evaluation\nprint(\"Evaluating Predictions...\")\nscore_class_specific(solution, submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:06:57.243818Z","iopub.execute_input":"2025-12-09T01:06:57.244177Z","iopub.status.idle":"2025-12-09T01:07:23.977920Z","shell.execute_reply.started":"2025-12-09T01:06:57.244148Z","shell.execute_reply":"2025-12-09T01:07:23.976628Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#00b7ff ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ff3300;\"><b> </b>Test Data Submission</p></div>","metadata":{}},{"cell_type":"code","source":"# Test Data Submission\ntest_df = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/test.csv')\ntest_submission = create_all_in_predictions(test_df, 'test')\n\ntest_submission.index.name = 'row_id'\ntest_submission.to_csv('submission.csv')\n\nprint(\"Submission file created successfully!\")\nprint(test_submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T01:07:27.828195Z","iopub.execute_input":"2025-12-09T01:07:27.828519Z","iopub.status.idle":"2025-12-09T01:07:27.869590Z","shell.execute_reply.started":"2025-12-09T01:07:27.828494Z","shell.execute_reply":"2025-12-09T01:07:27.868385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}