{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":99249,"databundleVersionId":12808057,"sourceType":"competition"},{"sourceId":12480719,"sourceType":"datasetVersion","datasetId":7875009}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":1779.037046,"end_time":"2025-07-15T18:28:31.044586","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-07-15T17:58:52.007540","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"cd2883db","cell_type":"markdown","source":"# 🐾 Cup-ybara My Submission\nThis notebook provides a fully self-contained submission for the Cup Ybara challenge.\nIt defines the required `BaseModel` interface, implements my `CustomModel`,\nand outputs a properly formatted `submission.csv` file.","metadata":{"papermill":{"duration":0.004377,"end_time":"2025-07-15T17:58:56.580473","exception":false,"start_time":"2025-07-15T17:58:56.576096","status":"completed"},"tags":[]},"attachments":{}},{"id":"e6613361","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":{"execution":{"iopub.status.busy":"2025-07-15T23:39:31.913291Z","iopub.execute_input":"2025-07-15T23:39:31.913676Z","iopub.status.idle":"2025-07-15T23:39:31.918522Z","shell.execute_reply.started":"2025-07-15T23:39:31.913653Z","shell.execute_reply":"2025-07-15T23:39:31.917575Z"},"papermill":{"duration":14.77456,"end_time":"2025-07-15T17:59:11.358686","exception":false,"start_time":"2025-07-15T17:58:56.584126","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"c0d204a0","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":{"execution":{"iopub.status.busy":"2025-07-15T23:39:31.919869Z","iopub.execute_input":"2025-07-15T23:39:31.920096Z","iopub.status.idle":"2025-07-15T23:39:31.938674Z","shell.execute_reply.started":"2025-07-15T23:39:31.920078Z","shell.execute_reply":"2025-07-15T23:39:31.937870Z"},"papermill":{"duration":0.013218,"end_time":"2025-07-15T17:59:11.377093","exception":false,"start_time":"2025-07-15T17:59:11.363875","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"48367ba6","cell_type":"code","source":"!ls /kaggle/input/cupybara/dataset/","metadata":{"execution":{"iopub.status.busy":"2025-07-15T23:39:31.939574Z","iopub.execute_input":"2025-07-15T23:39:31.939944Z","iopub.status.idle":"2025-07-15T23:39:32.098192Z","shell.execute_reply.started":"2025-07-15T23:39:31.939923Z","shell.execute_reply":"2025-07-15T23:39:32.097424Z"},"papermill":{"duration":0.144314,"end_time":"2025-07-15T17:59:11.524985","exception":false,"start_time":"2025-07-15T17:59:11.380671","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"52a1306f","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.003321,"end_time":"2025-07-15T17:59:11.532129","exception":false,"start_time":"2025-07-15T17:59:11.528808","status":"completed"},"tags":[]}},{"id":"e810d2c2","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/cypybara-t/trained_video_model_r2plus_insects_aug.pt\"\nVIDEO_FRAMES = 16\nFRAME_SIZE = 112  # Model input requirement\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"","metadata":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.099265Z","iopub.execute_input":"2025-07-15T23:39:32.099562Z","iopub.status.idle":"2025-07-15T23:39:32.104733Z","shell.execute_reply.started":"2025-07-15T23:39:32.099536Z","shell.execute_reply":"2025-07-15T23:39:32.104123Z"},"papermill":{"duration":0.014481,"end_time":"2025-07-15T17:59:11.550199","exception":false,"start_time":"2025-07-15T17:59:11.535718","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"269e37f0","cell_type":"code","source":"!rm -rf model_weights/ model_weights.zip submission.csv","metadata":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.106903Z","iopub.execute_input":"2025-07-15T23:39:32.107397Z","iopub.status.idle":"2025-07-15T23:39:32.266710Z","shell.execute_reply.started":"2025-07-15T23:39:32.107353Z","shell.execute_reply":"2025-07-15T23:39:32.265682Z"},"papermill":{"duration":9.203923,"end_time":"2025-07-15T17:59:20.757905","exception":false,"start_time":"2025-07-15T17:59:11.553982","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"a1cdc422","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":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.267776Z","iopub.execute_input":"2025-07-15T23:39:32.268012Z","iopub.status.idle":"2025-07-15T23:39:32.277560Z","shell.execute_reply.started":"2025-07-15T23:39:32.267988Z","shell.execute_reply":"2025-07-15T23:39:32.276775Z"},"papermill":{"duration":0.016223,"end_time":"2025-07-15T17:59:20.777881","exception":false,"start_time":"2025-07-15T17:59:20.761658","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"c51ed603","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.003351,"end_time":"2025-07-15T17:59:20.785025","exception":false,"start_time":"2025-07-15T17:59:20.781674","status":"completed"},"tags":[]}},{"id":"d28ddd6d","cell_type":"code","source":"!ls /kaggle/input/native-fauna-dummy-model","metadata":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.278435Z","iopub.execute_input":"2025-07-15T23:39:32.278757Z","iopub.status.idle":"2025-07-15T23:39:32.432948Z","shell.execute_reply.started":"2025-07-15T23:39:32.278730Z","shell.execute_reply":"2025-07-15T23:39:32.431969Z"},"papermill":{"duration":0.140682,"end_time":"2025-07-15T17:59:20.929316","exception":false,"start_time":"2025-07-15T17:59:20.788634","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"dc30b687","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.003606,"end_time":"2025-07-15T17:59:20.936889","exception":false,"start_time":"2025-07-15T17:59:20.933283","status":"completed"},"tags":[]}},{"id":"3e5705a1","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":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.434274Z","iopub.execute_input":"2025-07-15T23:39:32.434915Z","iopub.status.idle":"2025-07-15T23:39:32.441608Z","shell.execute_reply.started":"2025-07-15T23:39:32.434886Z","shell.execute_reply":"2025-07-15T23:39:32.440947Z"},"papermill":{"duration":0.014453,"end_time":"2025-07-15T17:59:20.955162","exception":false,"start_time":"2025-07-15T17:59:20.940709","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"9ab4f0ba","cell_type":"markdown","source":"## 🧠 Not 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.003532,"end_time":"2025-07-15T17:59:20.962529","exception":false,"start_time":"2025-07-15T17:59:20.958997","status":"completed"},"tags":[]}},{"id":"05c88d91","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        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":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.442449Z","iopub.execute_input":"2025-07-15T23:39:32.442689Z","iopub.status.idle":"2025-07-15T23:39:32.462620Z","shell.execute_reply.started":"2025-07-15T23:39:32.442657Z","shell.execute_reply":"2025-07-15T23:39:32.461995Z"},"papermill":{"duration":0.012743,"end_time":"2025-07-15T17:59:20.978927","exception":false,"start_time":"2025-07-15T17:59:20.966184","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"6cc6d2ba","cell_type":"markdown","source":"## 🚀 Run Inference and Generate Submission File","metadata":{"papermill":{"duration":0.003707,"end_time":"2025-07-15T17:59:20.986453","exception":false,"start_time":"2025-07-15T17:59:20.982746","status":"completed"},"tags":[]}},{"id":"d34eee4c","cell_type":"code","source":"model = CustomModel()\nmodel.generate_submission(eval_dir=EVAL_DIR)","metadata":{"execution":{"iopub.status.busy":"2025-07-15T23:39:32.463574Z","iopub.execute_input":"2025-07-15T23:39:32.463854Z","iopub.status.idle":"2025-07-15T23:50:35.685836Z","shell.execute_reply.started":"2025-07-15T23:39:32.463829Z","shell.execute_reply":"2025-07-15T23:50:35.684421Z"},"papermill":{"duration":1747.755533,"end_time":"2025-07-15T18:28:28.745686","exception":false,"start_time":"2025-07-15T17:59:20.990153","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"f3ac755a","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.003605,"end_time":"2025-07-15T18:28:28.753236","exception":false,"start_time":"2025-07-15T18:28:28.749631","status":"completed"},"tags":[]}},{"id":"b0dc2947","cell_type":"code","source":"print(\"scoring\")\ndf_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.033188,"end_time":"2025-07-15T18:28:28.790207","exception":false,"start_time":"2025-07-15T18:28:28.757019","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T23:50:35.686438Z","iopub.status.idle":"2025-07-15T23:50:35.686673Z","shell.execute_reply.started":"2025-07-15T23:50:35.686557Z","shell.execute_reply":"2025-07-15T23:50:35.686567Z"}},"outputs":[],"execution_count":null},{"id":"61a0d926","cell_type":"code","source":"f1_score(df_test[\"Species\"].values, df_submission[\"Species\"].values, average=\"weighted\")","metadata":{"papermill":{"duration":0.019709,"end_time":"2025-07-15T18:28:28.814087","exception":false,"start_time":"2025-07-15T18:28:28.794378","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T23:50:35.936838Z","iopub.execute_input":"2025-07-15T23:50:35.937581Z","iopub.status.idle":"2025-07-15T23:50:35.950971Z","shell.execute_reply.started":"2025-07-15T23:50:35.937554Z","shell.execute_reply":"2025-07-15T23:50:35.950013Z"}},"outputs":[],"execution_count":null}]}