{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":99249,"databundleVersionId":12808057,"sourceType":"competition"},{"sourceId":12477700,"sourceType":"datasetVersion","datasetId":7872783}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false},"papermill":{"default_parameters":{},"duration":10.590201,"end_time":"2025-04-15T19:54:42.874323","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-04-15T19:54:32.284122","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🐾 Cup-ybara Demo Submission\nThis notebook provides a fully self-contained baseline submission for the Cup Ybara challenge.\nIt defines the required `BaseModel` interface, implements a dummy `CustomModel`,\nand outputs a properly formatted `submission.csv` file.","metadata":{"papermill":{"duration":0.004766,"end_time":"2025-04-15T19:54:37.178312","exception":false,"start_time":"2025-04-15T19:54:37.173546","status":"completed"},"tags":[]},"attachments":{}},{"cell_type":"code","source":"import os\nimport random\nimport json\nimport joblib\nimport shutil\nimport pandas as pd\nimport numpy as np\nfrom typing import List\n\nimport torch\nimport cv2\nfrom torchvision.models.video import r2plus1d_18\nfrom torchvision import transforms\n\nfrom sklearn.metrics import f1_score","metadata":{"papermill":{"duration":3.607924,"end_time":"2025-04-15T19:54:40.790231","exception":false,"start_time":"2025-04-15T19:54:37.182307","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:02:42.811651Z","iopub.execute_input":"2025-07-15T17:02:42.812722Z","iopub.status.idle":"2025-07-15T17:02:55.533432Z","shell.execute_reply.started":"2025-07-15T17:02:42.812674Z","shell.execute_reply":"2025-07-15T17:02:55.532189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Hard coded class mappings\nidx_to_class = {\n    0: 'armadillo',\n    1: 'bird',\n    2: 'capybara',\n    3: 'cow',\n    4: 'dusky_legged_guan',\n    5: 'gray_brocket',\n    6: 'hare',\n    7: 'human',\n    8: 'insect',\n    9: 'margay',\n    10: 'no_animal',\n    11: 'skunk',\n    12: 'unknown_animal',\n    13: 'wild_boar'\n}\n\nclass_to_idx = {v: k for k, v in idx_to_class.items()}\nLABELS = [f\"'{idx_to_class[i]}'\" for i in sorted(idx_to_class)]\n\nprint(\"Labels:\", LABELS)","metadata":{"papermill":{"duration":0.010148,"end_time":"2025-04-15T19:54:40.804529","exception":false,"start_time":"2025-04-15T19:54:40.794381","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:02:55.534743Z","iopub.execute_input":"2025-07-15T17:02:55.535221Z","iopub.status.idle":"2025-07-15T17:02:55.543664Z","shell.execute_reply.started":"2025-07-15T17:02:55.535195Z","shell.execute_reply":"2025-07-15T17:02:55.542319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/input/cupybara/dataset/","metadata":{"papermill":{"duration":0.1382,"end_time":"2025-04-15T19:54:40.946351","exception":false,"start_time":"2025-04-15T19:54:40.808151","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:02:55.544788Z","iopub.execute_input":"2025-07-15T17:02:55.545123Z","iopub.status.idle":"2025-07-15T17:02:55.721674Z","shell.execute_reply.started":"2025-07-15T17:02:55.545087Z","shell.execute_reply":"2025-07-15T17:02:55.720388Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🏋️ About Train Mode\n\nThe boolean variable TRAIN is used to determine if the portion of the trainng code should be run or not. It will always be set to `False` when running the submission manually. Ensure that if a lengthy part of your notebook is used for training, that is toggled away by this variable","metadata":{"papermill":{"duration":0.003208,"end_time":"2025-04-15T19:54:40.953399","exception":false,"start_time":"2025-04-15T19:54:40.950191","status":"completed"},"tags":[]}},{"cell_type":"code","source":"DATASET_ROOT = \"/kaggle/input/cupybara/dataset/dataset/\"\nEVAL_DIR =  os.path.join(DATASET_ROOT,\"test\")  # Directory containing evaluation `.mp4` files\nTRAIN = True\n\n# Video model configuration\nMODEL_PATH = \"/kaggle/input/cupybara-model-r2plus-aug-combined-es/trained_video_model_r2plus_aug_combined_equal_spacing.pt\"\nVIDEO_FRAMES = 16\nFRAME_SIZE = 112  # Model input requirement\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"","metadata":{"papermill":{"duration":0.01155,"end_time":"2025-04-15T19:54:40.968329","exception":false,"start_time":"2025-04-15T19:54:40.956779","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:02:55.725986Z","iopub.execute_input":"2025-07-15T17:02:55.726330Z","iopub.status.idle":"2025-07-15T17:02:55.736743Z","shell.execute_reply.started":"2025-07-15T17:02:55.726299Z","shell.execute_reply":"2025-07-15T17:02:55.735455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm -rf model_weights/ model_weights.zip submission.csv\n!mkdir -p model_weights/\n!cp /kaggle/input/cupybara-model-r2plus-aug-combined-es/trained_video_model_r2plus_aug_combined_equal_spacing.pt model_weights/\n!zip -r model_weights.zip model_weights/","metadata":{"papermill":{"duration":0.129068,"end_time":"2025-04-15T19:54:41.101180","exception":false,"start_time":"2025-04-15T19:54:40.972112","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:02:55.738117Z","iopub.execute_input":"2025-07-15T17:02:55.738508Z","iopub.status.idle":"2025-07-15T17:03:05.709327Z","shell.execute_reply.started":"2025-07-15T17:02:55.738467Z","shell.execute_reply":"2025-07-15T17:03:05.708191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Video model transforms and functions\ntransform = transforms.Compose([\n    transforms.Resize((FRAME_SIZE, FRAME_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.43216, 0.394666, 0.37645],\n                         [0.22803, 0.22145, 0.216989]),\n])\n\ndef load_video_tensor(path, num_frames=VIDEO_FRAMES):\n    \"\"\"Load video as tensor with proper frame sampling\"\"\"\n    cap = cv2.VideoCapture(path)\n    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n\n    frame_idxs = np.linspace(0, total_frames - 1, num_frames).astype(int)\n    frames = []\n\n    for idx in range(total_frames):\n        ret, frame = cap.read()\n        if not ret:\n            break\n        if idx in frame_idxs:\n            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n            pil_image = transforms.ToPILImage()(frame)\n            frames.append(transform(pil_image))\n\n    cap.release()\n\n    if len(frames) < num_frames:\n        frames += [frames[-1]] * (num_frames - len(frames))  # pad with last frame if short\n\n    video_tensor = torch.stack(frames)  # [T, C, H, W]\n    return video_tensor.permute(1, 0, 2, 3)  # [C, T, H, W]\n\ndef load_model():\n    \"\"\"Load the trained video model\"\"\"\n    model = r2plus1d_18(pretrained=False)\n    model.fc = torch.nn.Linear(model.fc.in_features, len(class_to_idx))\n    model.load_state_dict(torch.load(MODEL_PATH, map_location=DEVICE))\n    model.eval()\n    return model.to(DEVICE)\n\nif TRAIN: \n    print(\"Video model setup complete. Training mode is enabled but not needed for submission.\")","metadata":{"papermill":{"duration":0.501626,"end_time":"2025-04-15T19:54:41.606615","exception":false,"start_time":"2025-04-15T19:54:41.104989","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:03:05.710791Z","iopub.execute_input":"2025-07-15T17:03:05.711155Z","iopub.status.idle":"2025-07-15T17:03:05.722865Z","shell.execute_reply.started":"2025-07-15T17:03:05.711096Z","shell.execute_reply":"2025-07-15T17:03:05.721760Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🤖 Uploading Trained Model as Public Dataset\n\n1. Manually download the generated `model_weights.zip` file from the `Output` section on the side panel.\n2. Upload the `model_weights.zip` file as a new **PUBLIC** dataset using the `Input` sectino on the side panel (Upload -> New Dataset).\n3. You can refer to the uploaded dataset by its name (all lowercase and replacing spaces with \"-\"). In our example: `Native Fauna Dummy Model` becomes `native-fauna-dummy-model`\n\n> Ensure that the dataset is **Public** and the license is Attributtion 4.0 International (CC BY 4.0)\n\n> If you had already created a dataset, you can upload a new version by clicking `Open in New Tab`, and then checking for updates. ","metadata":{"papermill":{"duration":0.003266,"end_time":"2025-04-15T19:54:41.613690","exception":false,"start_time":"2025-04-15T19:54:41.610424","status":"completed"},"tags":[]}},{"cell_type":"code","source":"!ls /kaggle/input/native-fauna-dummy-model","metadata":{"papermill":{"duration":0.137368,"end_time":"2025-04-15T19:54:41.754396","exception":false,"start_time":"2025-04-15T19:54:41.617028","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:03:05.724192Z","iopub.execute_input":"2025-07-15T17:03:05.724731Z","iopub.status.idle":"2025-07-15T17:03:05.976914Z","shell.execute_reply.started":"2025-07-15T17:03:05.724704Z","shell.execute_reply":"2025-07-15T17:03:05.975789Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧱 BaseModel Interface\nThis class defines the interface expected by the competition evaluators.\nYou must subclass `BaseModel` and implement `_load_model()` and `_predict()`.","metadata":{"papermill":{"duration":0.003212,"end_time":"2025-04-15T19:54:41.761331","exception":false,"start_time":"2025-04-15T19:54:41.758119","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class BaseModel:\n    def __init__(self):\n        self._load_model()\n\n    def _load_model(self) -> None:\n        raise NotImplementedError(\"You must implement `_load_model`.\")\n\n    def _predict(self, video_path: str) -> str:\n        raise NotImplementedError(\"You must implement `_predict`.\")\n\n    def predict(self, video_path: str) -> str:\n        return LABELS[self._predict(video_path)]\n\n    def generate_submission(self, eval_dir: str) -> None:\n\n        output_path: str = \"submission.csv\"\n        filenames: List[str] = sorted(os.listdir(eval_dir))\n        submission = []\n        for filename in filenames:\n            if filename.endswith(\".mp4\"):\n                video_path = os.path.join(eval_dir, filename)\n                prediction = self.predict(video_path)\n                submission.append((filename.split(\".\")[0], f\"{prediction}\"))  # Label in single quotes\n        \n        submission_df = pd.DataFrame(submission, columns=[\"Filename\", \"Species\"])\n        submission_df.to_csv(output_path, index=False)\n        print(f\"✅ Submission file saved to {output_path}\")","metadata":{"papermill":{"duration":0.013868,"end_time":"2025-04-15T19:54:41.778642","exception":false,"start_time":"2025-04-15T19:54:41.764774","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:03:05.978543Z","iopub.execute_input":"2025-07-15T17:03:05.979149Z","iopub.status.idle":"2025-07-15T17:03:05.988382Z","shell.execute_reply.started":"2025-07-15T17:03:05.978968Z","shell.execute_reply":"2025-07-15T17:03:05.987239Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧠 Dummy Model\nThis dummy model returns random predictions from the valid label list.\nReplace this with your actual model and logic.","metadata":{"papermill":{"duration":0.003314,"end_time":"2025-04-15T19:54:41.785660","exception":false,"start_time":"2025-04-15T19:54:41.782346","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class CustomModel(BaseModel):\n\n    def _load_model(self) -> None:\n        \"\"\"\n        Loads the video model from the model weights directory.\n        This guarantees that the model runs OFFLINE\n        \"\"\"\n        # Check if we're in the Kaggle environment with the uploaded dataset\n        if os.path.exists(\"/kaggle/input/native-fauna-dummy-model\"):\n            model_path = \"/kaggle/input/native-fauna-dummy-model/trained_video_model_r2plus_insects_aug_newset4.pt\"\n        else:\n            # Local testing environment\n            model_path = MODEL_PATH\n            \n        self.model = r2plus1d_18(pretrained=False)\n        self.model.fc = torch.nn.Linear(self.model.fc.in_features, len(class_to_idx))\n        self.model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n        self.model.eval()\n        self.model = self.model.to(DEVICE)\n        print(f\"✅ Model loaded from {model_path}\")\n\n    def _predict(self, video_path: str) -> int:\n        \"\"\"\n        Uses the loaded video model to generate a prediction.\n        \"\"\"\n        video = load_video_tensor(video_path).unsqueeze(0).to(DEVICE)  # [1, C, T, H, W]\n        with torch.no_grad():\n            outputs = self.model(video)\n            pred_idx = outputs.argmax(dim=1).item()\n        return pred_idx","metadata":{"papermill":{"duration":0.011979,"end_time":"2025-04-15T19:54:41.801296","exception":false,"start_time":"2025-04-15T19:54:41.789317","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:03:05.989444Z","iopub.execute_input":"2025-07-15T17:03:05.989777Z","iopub.status.idle":"2025-07-15T17:03:06.015783Z","shell.execute_reply.started":"2025-07-15T17:03:05.989748Z","shell.execute_reply":"2025-07-15T17:03:06.014742Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🚀 Run Inference and Generate Submission File","metadata":{"papermill":{"duration":0.003303,"end_time":"2025-04-15T19:54:41.808258","exception":false,"start_time":"2025-04-15T19:54:41.804955","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model = CustomModel()\nmodel.generate_submission(eval_dir=EVAL_DIR)","metadata":{"papermill":{"duration":0.286942,"end_time":"2025-04-15T19:54:42.098855","exception":false,"start_time":"2025-04-15T19:54:41.811913","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:03:06.018972Z","iopub.execute_input":"2025-07-15T17:03:06.019358Z","iopub.status.idle":"2025-07-15T17:35:39.782195Z","shell.execute_reply.started":"2025-07-15T17:03:06.019323Z","shell.execute_reply":"2025-07-15T17:35:39.781293Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🎯 Scoring the submission\n\nAt this point, we are ready to calculate the submission's score. Because the `test` dataset is split between the public and private leaderboard, the score below might not match exactly the one shown in the public leaderboard.\n\nTo generate your final submission, click `Submit` on the side panel and ensure that your internet connection is off.","metadata":{"papermill":{"duration":0.003489,"end_time":"2025-04-15T19:54:42.106099","exception":false,"start_time":"2025-04-15T19:54:42.102610","status":"completed"},"tags":[]}},{"cell_type":"code","source":"df_test = pd.read_csv(os.path.join(DATASET_ROOT, \"test.csv\")).sort_values(\"Filename\")\ndf_submission = pd.read_csv(\"submission.csv\").sort_values(\"Filename\")","metadata":{"papermill":{"duration":0.018065,"end_time":"2025-04-15T19:54:42.127811","exception":false,"start_time":"2025-04-15T19:54:42.109746","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:35:39.783167Z","iopub.execute_input":"2025-07-15T17:35:39.783540Z","iopub.status.idle":"2025-07-15T17:35:39.813821Z","shell.execute_reply.started":"2025-07-15T17:35:39.783517Z","shell.execute_reply":"2025-07-15T17:35:39.812810Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"f1_score(df_test[\"Species\"].values, df_submission[\"Species\"].values, average=\"weighted\")","metadata":{"papermill":{"duration":0.01886,"end_time":"2025-04-15T19:54:42.150570","exception":false,"start_time":"2025-04-15T19:54:42.131710","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T17:35:39.814904Z","iopub.execute_input":"2025-07-15T17:35:39.815183Z","iopub.status.idle":"2025-07-15T17:35:39.830570Z","shell.execute_reply.started":"2025-07-15T17:35:39.815157Z","shell.execute_reply":"2025-07-15T17:35:39.829413Z"}},"outputs":[],"execution_count":null}]}