{"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.12.0"}, "kaggle": {"accelerator": "nvidiaTeslaT4", "dataSources": [{"sourceId": 59156, "databundleVersionId": 13874099, "sourceType": "competition"}, {"sourceId": 13802936, "sourceType": "datasetVersion", "datasetId": 8788388}], "dockerImageVersionId": 31193, "isInternetEnabled": false, "language": "python", "sourceType": "notebook", "isGpuEnabled": true}}, "nbformat_minor": 4, "nbformat": 4, "cells": [{"cell_type": "markdown", "source": "# MABe Mouse Behavior Detection - Inference Pipeline\n\n**Strategy**: Load pretrained ResNet1D weights + run inference on test videos\n\n## Pipeline:\n1. Load model weights from dataset\n2. Extract 52-dim features from test tracking data\n3. Run frame-level predictions\n4. Post-process into behavior intervals\n5. Generate `submission.csv`\n\n**Expected Runtime**: ~30-60 minutes on Kaggle GPU", "metadata": {}}, {"cell_type": "code", "source": "# System setup\nimport sys\nimport os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport json\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nfrom scipy.ndimage import binary_dilation\n\n# Add model code to path\nsys.path.append('../input/mabe-resnet1d-weights/src')\n\n# Import custom modules\nfrom feature_extractor import MouseFeatureExtractor\nfrom resnet1d import ResNet1D\n\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Load model weights\nprint(\"\ud83d\udcc2 Loading model...\")\n\n# Load behavior mapping\nwith open('../input/mabe-resnet1d-weights/models/behavior_mapping.json', 'r') as f:\n    mapping_data = json.load(f)\n    \nbehavior_to_id = mapping_data['behavior_to_id']\nid_to_behavior = {v: k for k, v in behavior_to_id.items()}\nnum_classes = len(behavior_to_id)\n\nprint(f\"   Behaviors: {num_classes}\")\n\n# Create model\nmodel = ResNet1D(\n    input_dim=52,\n    embed_dim=256,\n    num_behaviors=num_classes\n)\n\n# Load checkpoint\ncheckpoint = torch.load(\n    '../input/mabe-resnet1d-weights/models/best_model.pt',\n    map_location=device\n)\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel = model.to(device)\nmodel.eval()\n\nprint(f\"   \u2705 Model loaded (epoch {checkpoint.get('epoch', 'N/A')})\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Load normalization stats\nprint(\"\ud83d\udcca Loading feature normalization stats...\")\nfeature_stats = np.load('../input/mabe-resnet1d-weights/feature_stats.npz')\nfeature_mean = feature_stats['mean']\nfeature_std = feature_stats['std']\nprint(f\"   Mean shape: {feature_mean.shape}\")\nprint(f\"   Std shape: {feature_std.shape}\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Load test metadata\nprint(\"\ud83d\udcf9 Loading test data...\")\ntest_df = pd.read_csv('../input/MABe-mouse-behavior-detection/test.csv')\nprint(f\"   Test videos: {len(test_df)}\")\nprint(test_df.head())", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Extract features for test videos\nprint(\"\\n\" + \"=\"*80)\nprint(\"\ud83d\udd27 EXTRACTING TEST FEATURES\")\nprint(\"=\"*80)\n\nextractor = MouseFeatureExtractor()\ntest_features = {}\n\nfor _, row in tqdm(test_df.iterrows(), total=len(test_df), desc=\"Extracting\"):\n    video_id = str(row['video_id'])\n    lab_id = row['lab_id']\n    \n    # Load tracking data - FIXED PATH: MABe-mouse-behavior-detection (capital M and B)\n    tracking_path = Path(f'../input/MABe-mouse-behavior-detection/test_tracking/{lab_id}/{video_id}.parquet')\n    \n    if not tracking_path.exists():\n        print(f\"   Warning: Tracking data not found for {video_id}\")\n        continue\n    \n    # Get frame count for reporting\n    tracking_df = pd.read_parquet(tracking_path)\n    n_frames = tracking_df['video_frame'].max() + 1\n    print(f\"\\n   Video {video_id}: {n_frames:,} frames\")\n    \n    # Extract features using the correct method\n    features = extractor.extract_features_from_file(str(tracking_path), fps=30.0)\n    \n    # Normalize\n    features = (features - feature_mean) / (feature_std + 1e-8)\n    \n    test_features[video_id] = features\n    print(f\"      Features shape: {features.shape}\")\n\nprint(f\"\\n\u2705 Feature extraction complete!\")\nprint(f\"   Total videos: {len(test_features)}\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Prediction functions\ndef predict_video(model, features, device, window_size=200, stride=10, batch_size=512):\n    \"\"\"Run frame-level predictions on a video\"\"\"\n    num_frames = len(features)\n    num_classes = model.classifier[-1].out_features\n    \n    predictions = np.zeros((num_frames, num_classes), dtype=np.float32)\n    counts = np.zeros(num_frames, dtype=np.float32)\n    \n    windows = []\n    window_ranges = []\n    \n    for start in range(0, num_frames - window_size + 1, stride):\n        end = start + window_size\n        \n        window = features[start:end]\n        windows.append(window)\n        window_ranges.append((start, end))\n        \n        if len(windows) == batch_size:\n            X = np.array(windows, dtype=np.float32)\n            X = np.transpose(X, (0, 2, 1))  # (batch, 52, window_size)\n            X = torch.from_numpy(X).to(device)\n            \n            with torch.no_grad():\n                logits = model(X, mode='classify')\n                probs = torch.softmax(logits, dim=1).cpu().numpy()\n            \n            # Assign predictions to ALL frames in the window, not just center\n            for prob, (start, end) in zip(probs, window_ranges):\n                predictions[start:end] += prob\n                counts[start:end] += 1\n            \n            windows = []\n            window_ranges = []\n    \n    # Process remaining\n    if windows:\n        X = np.array(windows, dtype=np.float32)\n        X = np.transpose(X, (0, 2, 1))\n        X = torch.from_numpy(X).to(device)\n        \n        with torch.no_grad():\n            logits = model(X, mode='classify')\n            probs = torch.softmax(logits, dim=1).cpu().numpy()\n        \n        for prob, (start, end) in zip(probs, window_ranges):\n            predictions[start:end] += prob\n            counts[start:end] += 1\n    \n    # Average predictions across overlapping windows\n    counts[counts == 0] = 1\n    predictions /= counts[:, np.newaxis]\n    \n    return predictions\n\n\ndef parse_behavior_label(behavior_str):\n    \"\"\"Parse behavior string into components\"\"\"\n    parts = behavior_str.split(',')\n    \n    if len(parts) == 3:\n        agent_str, target_str, action = parts\n        agent_id = int(agent_str.replace('mouse', ''))\n        target_id = int(target_str.replace('mouse', ''))\n        return agent_id, target_id, action\n    else:\n        return None, None, behavior_str\n\n\ndef predictions_to_intervals(predictions, id_to_behavior, \n                            min_length=15, confidence_threshold=0.3):\n    \"\"\"Convert frame-level predictions to behavior intervals\"\"\"\n    num_frames, num_classes = predictions.shape\n    intervals = []\n    \n    for class_id in range(num_classes):\n        behavior_str = id_to_behavior[class_id]\n        probs = predictions[:, class_id]\n        \n        # Threshold\n        binary = (probs > confidence_threshold).astype(np.uint8)\n        \n        # Smooth\n        binary = binary_dilation(binary, structure=np.ones(5))\n        \n        # Find intervals\n        in_interval = False\n        start = 0\n        \n        for frame in range(num_frames):\n            if binary[frame] and not in_interval:\n                start = frame\n                in_interval = True\n            elif not binary[frame] and in_interval:\n                end = frame - 1\n                length = end - start + 1\n                \n                if length >= min_length:\n                    agent_id, target_id, action = parse_behavior_label(behavior_str)\n                    if agent_id is not None:\n                        intervals.append((agent_id, target_id, action, start, end))\n                \n                in_interval = False\n        \n        # Handle interval at end\n        if in_interval:\n            end = num_frames - 1\n            length = end - start + 1\n            \n            if length >= min_length:\n                agent_id, target_id, action = parse_behavior_label(behavior_str)\n                if agent_id is not None:\n                    intervals.append((agent_id, target_id, action, start, end))\n    \n    return intervals\n\nprint(\"\u2705 Prediction functions defined\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Run predictions\nprint(\"\\n\" + \"=\"*80)\nprint(\"\ud83c\udfaf GENERATING PREDICTIONS\")\nprint(\"=\"*80)\n\nall_intervals = []\n\nfor video_id, features in tqdm(test_features.items(), desc=\"Predicting\"):\n    print(f\"\\n   Video {video_id}: {len(features):,} frames\")\n    \n    # Predict\n    predictions = predict_video(\n        model, features, device,\n        window_size=200,\n        stride=10,\n        batch_size=512\n    )\n    \n    # Debug: Check prediction statistics\n    max_probs = predictions.max(axis=1)\n    print(f\"      Prediction stats:\")\n    print(f\"        Max prob per frame: min={max_probs.min():.4f}, max={max_probs.max():.4f}, mean={max_probs.mean():.4f}\")\n    print(f\"        Frames above 0.20: {(max_probs > 0.20).sum()}\")\n    print(f\"        Frames above 0.15: {(max_probs > 0.15).sum()}\")\n    print(f\"        Frames above 0.10: {(max_probs > 0.10).sum()}\")\n    \n    # Convert to intervals with MORE LENIENT parameters\n    intervals = predictions_to_intervals(\n        predictions, id_to_behavior,\n        min_length=10,  # Reduced from 15 to 10 frames (0.33 seconds)\n        confidence_threshold=0.15  # Lowered from 0.2 to 0.15\n    )\n    \n    print(f\"      Found {len(intervals)} behavior intervals\")\n    \n    # Add video_id\n    for agent_id, target_id, action, start, stop in intervals:\n        all_intervals.append({\n            'video_id': video_id,\n            'agent_id': f'mouse{agent_id}',\n            'target_id': f'mouse{target_id}',\n            'action': action,\n            'start_frame': start,\n            'stop_frame': stop\n        })\n\nprint(f\"\\n\u2705 Predictions complete!\")\nprint(f\"   Total intervals: {len(all_intervals):,}\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Create submission file\nprint(\"\\n\" + \"=\"*80)\nprint(\"\ud83d\udcc4 CREATING SUBMISSION FILE\")\nprint(\"=\"*80)\n\nif all_intervals:\n    submission_df = pd.DataFrame(all_intervals)\n    \n    # Add row_id\n    submission_df.insert(0, 'row_id', range(len(submission_df)))\n    \n    # Reorder columns\n    submission_df = submission_df[[\n        'row_id', 'video_id', 'agent_id', 'target_id',\n        'action', 'start_frame', 'stop_frame'\n    ]]\nelse:\n    # Empty submission\n    submission_df = pd.DataFrame(columns=[\n        'row_id', 'video_id', 'agent_id', 'target_id',\n        'action', 'start_frame', 'stop_frame'\n    ])\n\n# Save\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(f\"\\n\u2705 Submission file created!\")\nprint(f\"   Predictions: {len(submission_df):,}\")\n\nif len(submission_df) > 0:\n    print(\"\\n\ud83d\udd0d Sample predictions:\")\n    print(submission_df.head(10))\n    \n    print(\"\\n\ud83d\udcca Action distribution:\")\n    action_counts = submission_df['action'].value_counts()\n    for action, count in action_counts.items():\n        print(f\"   {action}: {count:,}\")\nelse:\n    print(\"\\n\u26a0\ufe0f  No predictions generated!\")", "metadata": {}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "# Verify submission format\nprint(\"\\n\" + \"=\"*80)\nprint(\"\u2705 FINAL VALIDATION\")\nprint(\"=\"*80)\n\n# Check format\nrequired_columns = ['row_id', 'video_id', 'agent_id', 'target_id', 'action', 'start_frame', 'stop_frame']\nassert list(submission_df.columns) == required_columns, \"Columns mismatch!\"\nassert submission_df.isnull().sum().sum() == 0, \"Missing values found!\"\n\nprint(\"\u2705 All validation checks passed!\")\nprint(f\"\ud83d\udcc4 Submission ready: submission.csv ({len(submission_df):,} rows)\")\nprint(\"\\n\ud83c\udfaf Ready for Kaggle submission!\")", "metadata": {}, "outputs": [], "execution_count": null}]}