{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":292222910,"sourceType":"kernelVersion"}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# HELP NEEDED: TorchScript Submission Crashing on Hidden Test\n\n## Context\nI am trying to submit a **TorchScript** model (`.pt` file) for the CSIRO Biomass competition. \nMy pipeline works perfectly on the **Verification (Public)** test set (predicts 1 image, generates valid CSV).\nHowever, it consistently fails on the **Hidden Test Set** with `Notebook Threw Exception`.\n\n## What I have tried:\n1. **Memory Leak Check**: Ran a stress test loop (2500 iterations on public image). Memory usage is stable. **No OOM**.\n2. **Device Mismatch**: Model is traced on CUDA and inferred on CUDA. Works locally and on public runner.\n3. **Missing Files**: Added robust checks for `test_images/` directory and extensions (`.jpg`, `.jpeg`). Did not fix it.\n4. **Path Logic**: Reverted to the standard direct path concatenation used in successful public kernels. (This notebook).\n\n## The Problem\n- **Public Run**: Success. `submission.csv` is generated with valid floats.\n- **Submission**: Fails. `Notebook Threw Exception` (likely non-zero exit code).\nSince standard logs are not available for failed submissions, I am stuck.\n\n## The Pipeline Code\nBelow is the exact inference code. It manually implements the preprocessing (Resize -> Normalize) to keep dependencies minimal.","metadata":{}},{"cell_type":"code","source":"import os\nimport sys\nimport gc\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom PIL import Image\nfrom pathlib import Path\nimport traceback\n\n# ================= CONFIGURATION =================\n\n# Check for GPU\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# Data Paths\nDATA_DIR = '/kaggle/input/csiro-biomass/'\n# Model Path (Traced Ensemble, uploaded as dataset)\nSRC_MODEL = '/kaggle/input/csiro-p2-11-export-torchscript/ensemble_traced.pt'\n\n# Normalization Stats (Must match training)\n# Denocmalization (Target -> Raw)\nTARGET_MEANS = np.array([6.42, 11.8, 27.3, 45.5, 33.7])\nTARGET_STDS = np.array([11.9, 12.3, 26.1, 28.6, 25.5])\nTARGET_NAMES = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\n# Input Normalization (ImageNet Stats)\nIMG_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape(1, 3, 1, 1)\nIMG_STD = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape(1, 3, 1, 1)\n\nprint(f'Running on Device: {DEVICE}')","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def main():\n    try:\n        # --- STEP 1: LOAD METADATA ---\n        test_path = os.path.join(DATA_DIR, 'test.csv')\n        print(f'Reading {test_path}...')\n        test_df = pd.read_csv(test_path)\n        \n        # Construct Full Paths\n        # Standard logic: /kaggle/input/csiro-biomass/ + relative_path\n        test_df['full_path'] = DATA_DIR + test_df['image_path']\n        \n        # We iterate unique images to avoid redundant processing\n        unique_imgs = test_df['full_path'].unique()\n        print(f'Found {len(unique_imgs)} unique images to process.')\n\n        # --- STEP 2: LOAD MODEL ---\n        if not os.path.exists(SRC_MODEL):\n            raise FileNotFoundError(f'Model file not found at {SRC_MODEL}')\n            \n        print(f'Loading TorchScript model...')\n        model = torch.jit.load(SRC_MODEL, map_location=DEVICE)\n        model.eval()\n        print('Model loaded successfully.')\n\n        predictions = {}\n        fail_count = 0\n\n        # --- STEP 3: INFERENCE LOOP ---\n        print('Starting inference...')\n        for i, full_path in enumerate(unique_imgs):\n            \n            # Validate Existence\n            if os.path.exists(full_path):\n                try:\n                    # A. Load Image (PIL)\n                    img = Image.open(full_path).convert('RGB')\n                    \n                    # B. Resize (224x224)\n                    img = img.resize((224, 224), Image.BILINEAR)\n                    \n                    # C. To Tensor & Normalize (Numpy -> Torch)\n                    arr = np.array(img).astype(np.float32) / 255.0  # [0, 1]\n                    arr = arr.transpose(2, 0, 1)[None, ...]         # (1, C, H, W)\n                    arr = (arr - IMG_MEAN) / IMG_STD                # ImageNet Norm\n                    \n                    # D. Move to GPU\n                    t = torch.from_numpy(arr).to(DEVICE)\n                    \n                    # E. Forward Pass (Traced Model)\n                    with torch.no_grad():\n                        out = model(t)\n                        raw_preds = out.cpu().numpy()[0]\n                    \n                    # F. Post-Processing (Denormalize)\n                    final_preds = raw_preds * TARGET_STDS + TARGET_MEANS\n                    final_preds = np.maximum(final_preds, 0) # Clip negative\n                    \n                    # Store result\n                    predictions[full_path] = dict(zip(TARGET_NAMES, final_preds))\n                    \n                    # Cleanup to be safe\n                    del img, arr, t, out\n                    \n                except Exception as e:\n                    # Catch image-specific errors (corrupt file?)\n                    if fail_count < 5: print(f'Error processing {full_path}: {e}')\n                    fail_count += 1\n                    # Default to Zeros if error\n                    predictions[full_path] = {k: 0.0 for k in TARGET_NAMES}\n            else:\n                # Path not found\n                if fail_count < 5: print(f'Missing File: {full_path}')\n                fail_count += 1\n                predictions[full_path] = {k: 0.0 for k in TARGET_NAMES}\n            \n            # --- MEMORY MANAGEMENT ---\n            # Explicit GC every 50 images to prevent VRAM accumulation\n            if (i+1) % 50 == 0:\n                gc.collect()\n                torch.cuda.empty_cache()\n            \n            if (i+1) % 100 == 0:\n                print(f'Processed {i+1}/{len(unique_imgs)}')\n\n        print(f'Inference finished. Total Failures/Missing: {fail_count}')\n        \n        # --- STEP 4: GENERATE SUBMISSION ---\n        print('Generating submission.csv...')\n        sample_sub = pd.read_csv(os.path.join(DATA_DIR, 'sample_submission.csv'))\n        submission = sample_sub.copy()\n        \n        for i in range(len(submission)):\n            # Reconstruct the lookup key (full_path)\n            rel_path = test_df.iloc[i]['image_path']\n            f_path = DATA_DIR + rel_path\n            target_name = test_df.iloc[i]['target_name']\n            \n            # Lookup\n            pred_val = predictions.get(f_path, {}).get(target_name, 0.0)\n            submission.loc[i, 'target'] = pred_val\n            \n        submission.to_csv('submission.csv', index=False)\n        print('Saved submission.csv')\n        print(submission.head())\n\n    except Exception as e:\n        # Global Crash Handler\n        print(f'CRITICAL ERROR during execution: {e}')\n        traceback.print_exc()\n        \n        # Attempt to save a dummy submission so we get a specific Scoring Error \n        # instead of a generic \"Notebook Threw Exception\"\n        try:\n            print('Attempting to save fallback submission...')\n            sample = pd.read_csv(os.path.join(DATA_DIR, 'sample_submission.csv'))\n            sample.to_csv('submission.csv', index=False)\n        except:\n            print('Could not save fallback.')\n        \n        # Exit successfully to ensure logs are (hopefully) preserved or \n        # at least to signal it wasn't a SegFault.\n        sys.exit(0)\n\nif __name__ == '__main__':\n    main()","metadata":{},"outputs":[],"execution_count":null}]}