{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":125981,"databundleVersionId":14910697,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nimport heapq\nimport cv2\nimport json\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.transforms as T\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data import Dataset\nfrom torchvision import transforms\n\nimport cv2, os, torch, random, numpy as np\nfrom torch import nn, optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport cv2, random, numpy as np\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:21.229667Z","iopub.execute_input":"2025-12-28T06:52:21.230137Z","iopub.status.idle":"2025-12-28T06:52:29.290370Z","shell.execute_reply.started":"2025-12-28T06:52:21.230111Z","shell.execute_reply":"2025-12-28T06:52:29.289600Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Labels and Map images","metadata":{}},{"cell_type":"code","source":"img_add = \"/kaggle/input/the-blind-flight-synapse-drive-ps-1/SynapseDrive_Dataset/train/images/\"\nlabel_add = \"/kaggle/input/the-blind-flight-synapse-drive-ps-1/SynapseDrive_Dataset/train/labels/\"\n\nall_tiles = []\nall_labels = []\n\nmaps_list = []\nterrains_list = []\n\nfor p in range(1,21):\n    \n    img = cv2.imread(img_add+f\"{p:04}\"+\".png\")\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    with open(label_add+f\"{p:04}\"+\".json\", \"r\") as f:\n        label_data = json.load(f)\n\n    maps_list.append(img)\n    terrains_list.append(label_data[\"terrain\"])\n\n    label_grid = label_data[\"grid\"]\n    \n    for i in range(20):\n        for j in range(20):\n            y0, x0 = 5 + i*69, 5 + j*69\n            tile = img[y0:y0 + 64, x0:x0 + 64]\n            all_tiles.append(tile)\n            all_labels.append(label_grid[i][j])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:29.291543Z","iopub.execute_input":"2025-12-28T06:52:29.291881Z","iopub.status.idle":"2025-12-28T06:52:31.127772Z","shell.execute_reply.started":"2025-12-28T06:52:29.291857Z","shell.execute_reply":"2025-12-28T06:52:31.127022Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## making version 3 dataset for tiles","metadata":{}},{"cell_type":"code","source":"asset_address=\"/kaggle/input/the-blind-flight-synapse-drive-ps-1/SynapseDrive_Dataset/assets/\"\n\ntile_library = {\n    0:[\"desert/t1_sand.png\",\"forest/t0_dirt.png\",\"lab/t2_floor.png\"],\n    1:[\"desert/t1_cacti.png\",\"desert/t1_rocks.png\",\"forest/t0_tree.png\",\"lab/t2_plasma.png\",\"lab/t2_wall.png\"],\n    2:[\"desert/t1_quicksand.png\",\"forest/t0_puddle.png\",\"lab/t2_glue.png\"],\n    3:[\"desert/t1_rover.png\",\"forest/t0_startship.png\",\"lab/t2_drone.png\"],\n    4:[\"desert/t1_goal.png\",\"forest/t0_goal.png\",\"lab/t2_goal.png\"]\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:31.128734Z","iopub.execute_input":"2025-12-28T06:52:31.129082Z","iopub.status.idle":"2025-12-28T06:52:31.133313Z","shell.execute_reply.started":"2025-12-28T06:52:31.129046Z","shell.execute_reply":"2025-12-28T06:52:31.132727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment_tile(img):\n    aug = img.copy()\n    h, w = aug.shape[:2]\n\n    # ---- slight perspective tilt ----\n    if random.random() < 0.30:\n        delta = int(0.04 * min(h, w))\n\n        src = np.float32([\n            [0, 0], [w, 0], [w, h], [0, h]\n        ])\n\n        dst = np.float32([\n            [random.randint(-delta, delta), 0],\n            [w + random.randint(-delta, delta), 0],\n            [w + random.randint(-delta, delta), h],\n            [random.randint(-delta, delta), h]\n        ])\n\n        M = cv2.getPerspectiveTransform(src, dst)\n        aug = cv2.warpPerspective(aug, M, (w, h),\n                                  borderMode=cv2.BORDER_REFLECT)\n\n    # ---- mild blur ----\n    if random.random() < 0.25:\n        aug = cv2.GaussianBlur(aug, (3, 3), 0)\n\n    # ---- small random rotation (-6°..+6°) ----\n    if random.random() < 0.25:\n        angle = random.uniform(-6, 6)\n        M = cv2.getRotationMatrix2D((w/2, h/2), angle, 1.0)\n        aug = cv2.warpAffine(\n            aug, M, (w, h),\n            flags=cv2.INTER_LINEAR,\n            borderMode=cv2.BORDER_REFLECT\n        )\n\n    # ---- ONLY 90° or 270° rotations ----\n    if random.random() < 0.10:\n        k = random.choice([1, 3])   # 1=90°, 3=270°\n        aug = np.rot90(aug, k)\n\n    return aug","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:31.135037Z","iopub.execute_input":"2025-12-28T06:52:31.135297Z","iopub.status.idle":"2025-12-28T06:52:31.150018Z","shell.execute_reply.started":"2025-12-28T06:52:31.135277Z","shell.execute_reply":"2025-12-28T06:52:31.149243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"p1_tiles = []\np1_labels = []\n\np2_tiles = []\np2_labels = []\n\nfor cls in range(5):\n    for rel_path in tile_library[cls]:\n\n        path = asset_address + rel_path\n        \n        img = cv2.imread(path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        # ---- Phase 1: canonical tiles ----\n        p1_tiles.append(img)\n        p1_labels.append(cls)\n\n        # ---- Phase 2: augmented tiles ----\n        count = 200\n        if cls==4 or cls==4:\n            count=600\n        for _ in range(count):    # creates 100 synthetic copies\n            aug = augment_tile(img)\n            p2_tiles.append(aug)\n            p2_labels.append(cls)\n\np1_tiles = p1_tiles*20\np1_labels = p1_labels*20\n\nprint(len(p1_tiles), len(p2_tiles))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:31.150913Z","iopub.execute_input":"2025-12-28T06:52:31.151221Z","iopub.status.idle":"2025-12-28T06:52:51.029250Z","shell.execute_reply.started":"2025-12-28T06:52:31.151192Z","shell.execute_reply":"2025-12-28T06:52:51.028505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tile_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((64,64)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:51.030193Z","iopub.execute_input":"2025-12-28T06:52:51.030486Z","iopub.status.idle":"2025-12-28T06:52:51.034362Z","shell.execute_reply.started":"2025-12-28T06:52:51.030464Z","shell.execute_reply":"2025-12-28T06:52:51.033749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TileDataset(Dataset):\n    def __init__(self, tiles, labels, transform=None):\n        self.tiles = tiles\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.tiles)\n\n    def __getitem__(self, idx):\n        img = self.tiles[idx]\n        label = self.labels[idx]\n\n        if self.transform:\n            img = self.transform(img)\n\n        return img, label\n\n\np1_dataset = TileDataset(p1_tiles, p1_labels, transform=tile_transform)\np1_loader  = DataLoader(p1_dataset, batch_size=8, shuffle=True)\n\np2_dataset = TileDataset(p2_tiles, p2_labels, transform=tile_transform)\np2_loader  = DataLoader(p2_dataset, batch_size=64, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:51.035166Z","iopub.execute_input":"2025-12-28T06:52:51.035415Z","iopub.status.idle":"2025-12-28T06:52:51.054176Z","shell.execute_reply.started":"2025-12-28T06:52:51.035380Z","shell.execute_reply":"2025-12-28T06:52:51.053550Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TileCNN(nn.Module):\n    def __init__(self, num_classes=5):\n        super().__init__()\n        self.block1 = nn.Sequential(\n            nn.Conv2d(3,32,3,padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.Conv2d(32,32,3,padding=1),\n            nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2)\n        )\n        self.block2 = nn.Sequential(\n            nn.Conv2d(32,64,3,padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.Conv2d(64,64,3,padding=1),\n            nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2)\n        )\n        self.block3 = nn.Sequential(\n            nn.Conv2d(64,128,3,padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.Conv2d(128,128,3,padding=1),\n            nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2)\n        )\n        self.classifier = nn.Sequential(\n            nn.Linear(128*8*8,256), nn.ReLU(), nn.Dropout(0.4), nn.Linear(256,num_classes)\n        )\n    def forward(self,x):\n        x=self.block1(x); x=self.block2(x); x=self.block3(x)\n        x=torch.flatten(x,1)\n        return self.classifier(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:51.054933Z","iopub.execute_input":"2025-12-28T06:52:51.055181Z","iopub.status.idle":"2025-12-28T06:52:51.068526Z","shell.execute_reply.started":"2025-12-28T06:52:51.055162Z","shell.execute_reply":"2025-12-28T06:52:51.067895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, criterion):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n\n    for images, labels in loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n        _, preds = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (preds == labels).sum().item()\n\n    avg_loss = running_loss / len(loader)\n    accuracy = 100.0 * correct / total\n    return avg_loss, accuracy\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:51.069375Z","iopub.execute_input":"2025-12-28T06:52:51.069609Z","iopub.status.idle":"2025-12-28T06:52:51.081864Z","shell.execute_reply.started":"2025-12-28T06:52:51.069591Z","shell.execute_reply":"2025-12-28T06:52:51.081313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel_tile = TileCNN().to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=torch.tensor(\n    [1.0, 1.2, 1.2, 4.0, 4.0], device=device\n))\noptimizer = optim.Adam(model_tile.parameters(), lr=1e-3)\n\nprint(\"Phase-1 training...\")\nfor epoch in range(10):\n    loss, acc = train_one_epoch(model_tile, p1_loader, optimizer, criterion)\n    print(f\"Epoch {epoch+1}/10  Loss={loss:.4f}  Acc={acc:.2f}%\")\n\ntorch.save(model_tile.state_dict(), \"tile_cnn.pth\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:52:51.084114Z","iopub.execute_input":"2025-12-28T06:52:51.084351Z","iopub.status.idle":"2025-12-28T06:53:10.555740Z","shell.execute_reply.started":"2025-12-28T06:52:51.084331Z","shell.execute_reply":"2025-12-28T06:53:10.555113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_tile.load_state_dict(torch.load(\"tile_cnn.pth\", map_location=device))\n\noptimizer = optim.Adam(model_tile.parameters(), lr=1e-4)\n\nprint(\"Phase-2 fine-tuning...\")\nfor epoch in range(20):\n    loss, acc = train_one_epoch(model_tile, p2_loader, optimizer, criterion)\n    print(f\"Epoch {epoch+1}/20  Loss={loss:.4f}  Acc={acc:.2f}%\")\n\ntorch.save(model_tile.state_dict(), \"tile_cnn.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T06:53:10.556577Z","iopub.execute_input":"2025-12-28T06:53:10.556882Z","iopub.status.idle":"2025-12-28T07:01:44.203702Z","shell.execute_reply.started":"2025-12-28T06:53:10.556860Z","shell.execute_reply":"2025-12-28T07:01:44.203037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_tiles(model, tiles, labels, device):\n    model.eval()\n\n    # preprocess all tiles\n    X = torch.stack([tile_transform(t) for t in tiles]).to(device)\n    y = torch.tensor(labels).to(device)\n\n    with torch.no_grad():\n        outputs = model(X)\n        preds = outputs.argmax(1)\n\n    correct = (preds == y).sum().item()\n    acc = correct / len(labels) * 100.0\n\n    print(f\"Accuracy: {acc:.2f}%  ({correct}/{len(labels)})\")\n\n    return preds.cpu().numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:44.204580Z","iopub.execute_input":"2025-12-28T07:01:44.204895Z","iopub.status.idle":"2025-12-28T07:01:44.210031Z","shell.execute_reply.started":"2025-12-28T07:01:44.204871Z","shell.execute_reply":"2025-12-28T07:01:44.209302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_tile.load_state_dict(torch.load(\"tile_cnn.pth\", map_location=device))\n\npreds = evaluate_tiles(\n    model_tile,\n    all_tiles,\n    all_labels,\n    device\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:44.210899Z","iopub.execute_input":"2025-12-28T07:01:44.211182Z","iopub.status.idle":"2025-12-28T07:01:48.492987Z","shell.execute_reply.started":"2025-12-28T07:01:44.211162Z","shell.execute_reply":"2025-12-28T07:01:48.491784Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## making the version 2 dataset for tiles","metadata":{}},{"cell_type":"markdown","source":"## Augmentation function","metadata":{}},{"cell_type":"markdown","source":"## Dataset builder","metadata":{}},{"cell_type":"markdown","source":"# Terrain classifier \n## Creating dataset","metadata":{}},{"cell_type":"code","source":"CLASS_MAP = {\"lab\": 0, \"forest\": 1, \"desert\": 2}\n\nclass TerrainDataset(Dataset):\n    def __init__(self, maps_list, terrains_list):\n        self.maps = maps_list\n        self.labels = [CLASS_MAP[t] for t in terrains_list]\n\n    def __len__(self):\n        return len(self.maps)\n\n    def __getitem__(self, idx):\n        img = cv2.resize(self.maps[idx], (256, 256))\n        img = torch.tensor(img).permute(2, 0, 1).float() / 255.0\n        \n        label = torch.tensor(self.labels[idx])\n        return img, label\n\nterrain_dataset = TerrainDataset(maps_list, terrains_list)\nterrain_loader = DataLoader(terrain_dataset, batch_size=4, shuffle=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:48.493629Z","iopub.execute_input":"2025-12-28T07:01:48.493837Z","iopub.status.idle":"2025-12-28T07:01:48.501830Z","shell.execute_reply.started":"2025-12-28T07:01:48.493816Z","shell.execute_reply":"2025-12-28T07:01:48.500972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TerrainCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        # ---- Feature extractor ----\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),   # -> (32, 256, 256)\n            nn.ReLU(),\n            nn.MaxPool2d(2),                  # -> (32, 128, 128)\n\n            nn.Conv2d(32, 64, 3, padding=1),  # -> (64, 128, 128)\n            nn.ReLU(),\n            nn.MaxPool2d(2),                  # -> (64, 64, 64)\n\n            nn.Conv2d(64, 128, 3, padding=1), # -> (128, 64, 64)\n            nn.ReLU(),\n            nn.MaxPool2d(2),                  # -> (128, 32, 32)\n\n            nn.Conv2d(128, 256, 3, padding=1),# -> (256, 32, 32)\n            nn.ReLU(),\n            nn.MaxPool2d(2),                  # -> (256, 16, 16)\n        )\n\n        # ---- Classifier head ----\n        self.classifier = nn.Sequential(\n            nn.Linear(256 * 16 * 16, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, 3)     # 3 classes: lab / forest / desert\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = torch.flatten(x, 1)\n        return self.classifier(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:48.502740Z","iopub.execute_input":"2025-12-28T07:01:48.502997Z","iopub.status.idle":"2025-12-28T07:01:48.516723Z","shell.execute_reply.started":"2025-12-28T07:01:48.502949Z","shell.execute_reply":"2025-12-28T07:01:48.516072Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Loss / optimizer","metadata":{}},{"cell_type":"code","source":"\nmodel_terrain = TerrainCNN().to(device)\nterrain_criterion = nn.CrossEntropyLoss()\nterrain_optimizer = optim.Adam(model_terrain.parameters(), lr=1e-3)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:48.517545Z","iopub.execute_input":"2025-12-28T07:01:48.517787Z","iopub.status.idle":"2025-12-28T07:01:48.635141Z","shell.execute_reply.started":"2025-12-28T07:01:48.517759Z","shell.execute_reply":"2025-12-28T07:01:48.634462Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Epoch function (common for both models)","metadata":{}},{"cell_type":"markdown","source":"## Training loop for terrain model","metadata":{}},{"cell_type":"code","source":"EPOCHS_1 = 15\n\nfor epoch in range(EPOCHS_1):\n    loss, acc = train_one_epoch(\n        model_terrain,\n        terrain_loader,\n        terrain_optimizer,\n        terrain_criterion\n    )\n\n    print(\n        f\"Epoch [{epoch+1}/{EPOCHS_1}] \"\n        f\"Loss: {loss:.4f} | Accuracy: {acc:.2f}%\"\n    )\n\n####Save the model\ntorch.save(model_terrain.state_dict(), \"terrain_cnn.pth\")\nprint(\"Model saved successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:48.636006Z","iopub.execute_input":"2025-12-28T07:01:48.636282Z","iopub.status.idle":"2025-12-28T07:01:50.069089Z","shell.execute_reply.started":"2025-12-28T07:01:48.636251Z","shell.execute_reply":"2025-12-28T07:01:50.068497Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Terrain prediction","metadata":{}},{"cell_type":"code","source":"ID_TO_TERRAIN = {0:\"lab\", 1:\"forest\", 2:\"desert\"}\n\ndef predict_terrain(model, img, device):\n    t = cv2.resize(img, (256, 256))\n    t = torch.tensor(t).permute(2,0,1).float().unsqueeze(0)/255.0\n    t = t.to(device)\n\n    with torch.no_grad():\n        pred = model(t).argmax(1).item()\n\n    return ID_TO_TERRAIN[pred]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.069955Z","iopub.execute_input":"2025-12-28T07:01:50.070230Z","iopub.status.idle":"2025-12-28T07:01:50.074977Z","shell.execute_reply.started":"2025-12-28T07:01:50.070208Z","shell.execute_reply":"2025-12-28T07:01:50.074285Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Tiles classifier\n## Create Dataset","metadata":{}},{"cell_type":"markdown","source":"## Loss / Optimizer","metadata":{}},{"cell_type":"markdown","source":"## Model initialisation","metadata":{}},{"cell_type":"code","source":"model_tile = TileCNN().to(device)\nmodel_tile.load_state_dict(torch.load(\"tile_cnn.pth\", map_location=device))\nmodel_tile.eval()\n\nmodel_terrain = TerrainCNN().to(device)\nmodel_terrain.load_state_dict(torch.load(\"terrain_cnn.pth\", map_location=device))\nmodel_terrain.eval()\n\nprint(\"model initialised\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.075823Z","iopub.execute_input":"2025-12-28T07:01:50.076134Z","iopub.status.idle":"2025-12-28T07:01:50.258881Z","shell.execute_reply.started":"2025-12-28T07:01:50.076102Z","shell.execute_reply":"2025-12-28T07:01:50.258334Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Path calculation begins here\n## Making cost grid","metadata":{}},{"cell_type":"code","source":"INF = 1e9\n\nTERRAIN_COSTS = {\n    \"lab\":    {\"walkable\":1.0,\"hazard\":3.0,\"start\":1.0,\"goal\":2.0,\"wall\":INF},\n    \"forest\": {\"walkable\":1.5,\"hazard\":2.8,\"start\":1.5,\"goal\":2.5,\"wall\":INF},\n    \"desert\": {\"walkable\":1.2,\"hazard\":3.7,\"start\":1.2,\"goal\":2.2,\"wall\":INF},\n}\n\ndef build_cost_map(pred_grid, boost_grid, terrain):\n    c = TERRAIN_COSTS[terrain]\n    cost_map = np.zeros_like(pred_grid, dtype=float)\n\n    for i in range(20):\n        for j in range(20):\n            tile = pred_grid[i,j]\n\n            if tile == 1:\n                cost_map[i,j] = INF\n                continue\n\n            elif tile == 0: base = c[\"walkable\"]\n            elif tile == 2: base = c[\"hazard\"]\n            elif tile == 3: base = c[\"start\"]\n            elif tile == 4: base = c[\"goal\"]\n            else: base = c[\"hazard\"]\n\n            cost_map[i,j] = base - boost_grid[i,j]\n\n    return cost_map","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.259672Z","iopub.execute_input":"2025-12-28T07:01:50.259919Z","iopub.status.idle":"2025-12-28T07:01:50.265829Z","shell.execute_reply.started":"2025-12-28T07:01:50.259886Z","shell.execute_reply":"2025-12-28T07:01:50.265276Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Search implementation","metadata":{}},{"cell_type":"code","source":"MOVES = [(0, 1), (0, -1), (1, 0), (-1, 0)]\n\nMOVE_NAMES = {(0, 1):\"R\", (0, -1):\"L\", (1, 0):\"D\", (-1, 0):\"U\"}\n\ndef heuristic(a, b):\n    # Manhattan distance\n    return abs(a[0] - b[0]) + abs(a[1] - b[1])\n\ndef astar(cost_map, start, goal):\n    rows, cols = cost_map.shape\n\n    # priority queue (f, g, position, parent)\n    pq = []\n    heapq.heappush(pq, (0, 0, start, None))\n\n    visited = set()\n    parent = {}   # node -> (prev_node, move)\n\n    while pq:\n        f, g, node, prev = heapq.heappop(pq)\n        if node in visited:\n            continue\n\n        visited.add(node)\n        parent[node] = prev\n\n        if node == goal:\n            break\n\n        x, y = node\n\n        for dx, dy in MOVES:\n            nx, ny = x + dx, y + dy\n\n            # bounds check\n            if not (0 <= nx < rows and 0 <= ny < cols):\n                continue\n\n            # wall check\n            if cost_map[nx, ny] >= 1e9:\n                continue\n\n            new_g = g + cost_map[nx, ny]\n            h = heuristic((nx, ny), goal)\n            new_f = new_g + h\n\n            # push neighbor\n            heapq.heappush(\n                pq,\n                (new_f, new_g, (nx, ny), (node, (dx, dy)))\n            )\n\n    # reconstruct path from parent dict\n    path = []\n    moves = []\n    node = goal\n\n    if node not in parent:\n        return None, None,None   # no path found\n\n    while node != start:\n        prev, move = parent[node]\n        path.append(node)\n        moves.append(MOVE_NAMES[move])\n        node = prev\n\n    path.append(start)\n    path.reverse()\n    moves.reverse()\n\n    total_cost = sum(cost_map[x, y] for x, y in path[1:])  # entering costs\n\n    return path, \"\".join(moves), total_cost\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.266726Z","iopub.execute_input":"2025-12-28T07:01:50.267056Z","iopub.status.idle":"2025-12-28T07:01:50.286220Z","shell.execute_reply.started":"2025-12-28T07:01:50.267025Z","shell.execute_reply":"2025-12-28T07:01:50.285706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_tiles(model, img, device):\n\n    rectified = deskew_and_warp(img)\n    tiles = extract_tiles_from_rectified(rectified)\n    \n    model.eval()\n    tiles_np = np.stack(tiles, axis=0)          # shape: (400, H, W, 3)\n    X = torch.from_numpy(tiles_np).permute(0,3,1,2).float() / 255.0\n    X = X.to(device)\n\n    with torch.no_grad():\n        preds = model(X).argmax(1).cpu().numpy()\n        \n    return preds.reshape(20,20), tiles\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.287047Z","iopub.execute_input":"2025-12-28T07:01:50.287267Z","iopub.status.idle":"2025-12-28T07:01:50.302815Z","shell.execute_reply.started":"2025-12-28T07:01:50.287247Z","shell.execute_reply":"2025-12-28T07:01:50.302215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nGRID_SIZE = 20   # 20x20 maps\n\ndef order_points(pts):\n    pts = np.array(pts, dtype=\"float32\")\n    s = pts.sum(axis=1)\n    diff = np.diff(pts, axis=1)\n\n    tl = pts[np.argmin(s)]\n    br = pts[np.argmax(s)]\n    tr = pts[np.argmin(diff)]\n    bl = pts[np.argmax(diff)]\n\n    return np.array([tl, tr, br, bl], dtype=\"float32\")\n\n\ndef deskew_and_warp(img):\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    blur = cv2.GaussianBlur(gray, (3,3), 0)\n\n    edges = cv2.Canny(blur, 40, 120)\n\n    # dilate edges so grid lines form connected components\n    kernel = np.ones((3,3), np.uint8)\n    edges = cv2.dilate(edges, kernel, iterations=2)\n\n    cnts, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    if not cnts:\n        return img\n\n    # Use MIN AREA RECTANGLE (robust even if not perfectly rectangular)\n    c = max(cnts, key=cv2.contourArea)\n    rect = cv2.minAreaRect(c)\n    box = cv2.boxPoints(rect)\n    box = np.int32(box)\n\n    ordered = order_points(box)\n\n    # Target resolution: exact 20x20 grid, keep approximate aspect\n    TARGET = 20 * 64      # 1280px\n    dst = np.array([\n        [0,      0],\n        [TARGET, 0],\n        [TARGET, TARGET],\n        [0,      TARGET]\n    ], dtype=\"float32\")\n\n    M = cv2.getPerspectiveTransform(ordered, dst)\n    warped = cv2.warpPerspective(img, M, (TARGET, TARGET))\n\n    return warped\n\n\ndef extract_tiles_from_rectified(rectified, grid=20):\n    H, W, _ = rectified.shape\n\n    tile_h = H / grid\n    tile_w = W / grid\n\n    tiles = []\n\n    # we purposely crop only the INNER 80% region of each cell\n    inner = 0.80\n\n    for i in range(grid):\n        for j in range(grid):\n\n            cy = int((i + 0.5) * tile_h)   # center y\n            cx = int((j + 0.5) * tile_w)   # center x\n\n            half = int(min(tile_h, tile_w) * inner / 2)\n\n            y0, y1 = cy - half, cy + half\n            x0, x1 = cx - half, cx + half\n\n            tile = rectified[y0:y1, x0:x1]\n            tile = cv2.resize(tile, (64, 64))\n\n            tiles.append(tile)\n\n    return tiles\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.303586Z","iopub.execute_input":"2025-12-28T07:01:50.303845Z","iopub.status.idle":"2025-12-28T07:01:50.320415Z","shell.execute_reply.started":"2025-12-28T07:01:50.303816Z","shell.execute_reply":"2025-12-28T07:01:50.319841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_pipeline(img_path, boost_path, model_tiles, model_terrain, device):\n    img = cv2.imread(img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    pred_grid, tiles = predict_tiles(model_tiles, img, device)\n    terrain = predict_terrain(model_terrain, img, device)\n    \n    show_tiles_with_marks(tiles, pred_grid)\n\n    \n    with open(boost_path) as f:\n        boost_grid = np.array(json.load(f)[\"boost\"])\n\n    start_candidates = np.argwhere(pred_grid == 3)\n    goal_candidates  = np.argwhere(pred_grid == 4)\n\n    if len(start_candidates) == 0 or len(goal_candidates) == 0:\n        return None, \"\", float(\"inf\"), terrain   # signal failure safely\n\n    start = tuple(start_candidates[0])\n    goal  = tuple(goal_candidates[0])\n\n    \n    cost_map = build_cost_map(pred_grid, boost_grid, terrain)\n    path, moves, cost = astar(cost_map, start, goal)\n    \n\n    return path, moves, cost, terrain","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:01:50.321194Z","iopub.execute_input":"2025-12-28T07:01:50.321492Z","iopub.status.idle":"2025-12-28T07:01:50.338488Z","shell.execute_reply.started":"2025-12-28T07:01:50.321472Z","shell.execute_reply":"2025-12-28T07:01:50.337911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom matplotlib.patches import Rectangle\nimport numpy as np\n\ndef show_tiles_with_marks(tiles, pred_grid):\n    tiles = np.array(tiles)          # (400, 64, 64, 3)\n    assert tiles.shape[0] == 400, \"Expected 400 tiles\"\n    assert pred_grid.shape == (20,20), \"Expected 20x20 pred grid\"\n\n    fig, axes = plt.subplots(20, 20, figsize=(10,10))\n\n    idx = 0\n    for i in range(20):\n        for j in range(20):\n\n            ax = axes[i, j]\n            ax.imshow(tiles[idx])\n            ax.axis(\"off\")\n\n            cls = pred_grid[i, j]\n\n            # START (class 3) → red border\n            if cls == 3:\n                print(\"start found\")\n                ax.add_patch(Rectangle(\n                    (0, 0), 63, 63,\n                    fill=False, lw=2, edgecolor=\"red\"\n                ))\n\n            # GOAL (class 4) → green border\n            if cls == 4:\n                print(\"goal found\")\n                ax.add_patch(Rectangle(\n                    (0, 0), 63, 63,\n                    fill=False, lw=2, edgecolor=\"lime\"\n                ))\n                \n            # Walkable (class 0) → green border\n            if cls == 1:\n                print(\"wall found\")\n                ax.add_patch(Rectangle(\n                    (0, 0), 63, 63,\n                    fill=False, lw=2, edgecolor=\"red\"\n                ))\n\n            idx += 1\n\n    plt.tight_layout()\n    plt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:09:13.504271Z","iopub.execute_input":"2025-12-28T07:09:13.504929Z","iopub.status.idle":"2025-12-28T07:09:13.511794Z","shell.execute_reply.started":"2025-12-28T07:09:13.504902Z","shell.execute_reply":"2025-12-28T07:09:13.511222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_add = \"/kaggle/input/the-blind-flight-synapse-drive-ps-1/SynapseDrive_Dataset/test/images/\"\nboost_add = \"/kaggle/input/the-blind-flight-synapse-drive-ps-1/SynapseDrive_Dataset/test/velocities/\"\n\nind_list = []\nmoves_list = []\n\nfor i in range(3,4):\n    img_path = img_add + f\"{i:04}\" + \".png\"\n    boost_path = boost_add + f\"{i:04}\" + \".json\"\n\n    \n    path, moves, cost, terrain = run_pipeline(\n        img_path,\n        boost_path,\n        model_tile,\n        model_terrain,\n        device\n    )\n    \n    ind_list.append(f\"{i:04}\")\n    moves_list.append(moves)\n    if i%100 ==0:\n        print(f\"path {i-99:04} to {i:04} done\")\n\nimport pandas as pd\n\ndata={\n    \"image_id\" : ind_list,\n    \"path\" : moves_list\n}\n\ndf = pd.DataFrame(data)\n\noutput_filename = 'submission.csv'\ndf.to_csv(output_filename, index=False)\n\nprint(f\"Successfully converted lists to '{output_filename}'\")\nprint(df.to_string())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T07:09:18.170928Z","iopub.execute_input":"2025-12-28T07:09:18.171475Z","iopub.status.idle":"2025-12-28T07:09:23.292353Z","shell.execute_reply.started":"2025-12-28T07:09:18.171448Z","shell.execute_reply":"2025-12-28T07:09:23.291790Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}