{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":33264,"databundleVersionId":2979241,"sourceType":"competition"}],"dockerImageVersionId":30153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 事前準備","metadata":{}},{"cell_type":"code","source":"!pip install segmentation-models-pytorch","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:00.964097Z","iopub.execute_input":"2025-05-22T04:59:00.964335Z","iopub.status.idle":"2025-05-22T04:59:07.460268Z","shell.execute_reply.started":"2025-05-22T04:59:00.964253Z","shell.execute_reply":"2025-05-22T04:59:07.459593Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os.path as osp\nfrom glob import glob\nimport json\nimport random\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nimport torch.optim as optim\nimport torch.nn as nn\nimport torch.utils.data as data\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport segmentation_models_pytorch as smp\nfrom segmentation_models_pytorch.encoders import get_preprocessing_fn","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-05-22T04:59:07.461906Z","iopub.execute_input":"2025-05-22T04:59:07.462121Z","iopub.status.idle":"2025-05-22T04:59:10.537876Z","shell.execute_reply.started":"2025-05-22T04:59:07.462092Z","shell.execute_reply":"2025-05-22T04:59:10.537113Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rootpath = \"../input/driving-segmentation-inclass-competition/\"\ntrain_dir = osp.join(rootpath, \"train\")\ntest_dir = osp.join(rootpath, \"test\")\nconfig_path = osp.join(rootpath, \"config.json\")\nwith open(config_path) as f:\n    config = json.load(f)\n\nclass_num = len(config['labels'])\nimg_size = (1920, 1080)","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.539154Z","iopub.execute_input":"2025-05-22T04:59:10.539404Z","iopub.status.idle":"2025-05-22T04:59:10.546427Z","shell.execute_reply.started":"2025-05-22T04:59:10.539373Z","shell.execute_reply":"2025-05-22T04:59:10.545609Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_map = {}\nfor label_dict in config['labels']:\n    labels_map[label_dict['id']] = label_dict\n    del label_dict['id']\nlabels_map","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.548220Z","iopub.execute_input":"2025-05-22T04:59:10.548464Z","iopub.status.idle":"2025-05-22T04:59:10.565467Z","shell.execute_reply.started":"2025-05-22T04:59:10.548439Z","shell.execute_reply":"2025-05-22T04:59:10.564762Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_list, anno_list = sorted(glob(osp.join(train_dir, 'frames/*'))), sorted(glob(osp.join(train_dir, 'labels/*')))\nlen(img_list), len(anno_list)","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.566326Z","iopub.execute_input":"2025-05-22T04:59:10.566510Z","iopub.status.idle":"2025-05-22T04:59:10.600889Z","shell.execute_reply.started":"2025-05-22T04:59:10.566479Z","shell.execute_reply":"2025-05-22T04:59:10.600312Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Datasetの作成","metadata":{"execution":{"iopub.status.busy":"2021-12-15T20:42:00.878921Z","iopub.execute_input":"2021-12-15T20:42:00.879252Z","iopub.status.idle":"2021-12-15T20:42:00.884049Z","shell.execute_reply.started":"2021-12-15T20:42:00.87922Z","shell.execute_reply":"2021-12-15T20:42:00.882903Z"}}},{"cell_type":"code","source":"input_size = 512\nencoder = 'efficientnet-b4'\n\npreprocess_input = get_preprocessing_fn(encoder, pretrained='imagenet')\n\ntrain_transform = A.Compose([\n    A.Rotate((-10, 10)),\n    A.HorizontalFlip(p=0.5),\n    A.Resize(input_size, input_size),\n    A.Lambda(image=preprocess_input),\n    ToTensorV2(),\n])\n\nval_transform = A.Compose([\n    A.Resize(input_size, input_size),\n    A.Lambda(image=preprocess_input),\n    ToTensorV2(),\n])","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.601666Z","iopub.execute_input":"2025-05-22T04:59:10.601846Z","iopub.status.idle":"2025-05-22T04:59:10.606500Z","shell.execute_reply.started":"2025-05-22T04:59:10.601824Z","shell.execute_reply":"2025-05-22T04:59:10.605810Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Dataset(data.Dataset):\n\n    def __init__(self, img_list, anno_list=None, transform=None):\n        self.img_list = img_list\n        self.anno_list = anno_list\n        self.transform = transform\n\n    def __len__(self):\n        '''画像の枚数を返す'''\n        return len(self.img_list)\n\n    def __getitem__(self, index):\n        '''\n        前処理をした画像のTensor形式のデータとアノテーションを取得\n        '''\n        image_file_path = self.img_list[index]\n        img = cv2.imread(image_file_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        anno_class_img = torch.tensor(float('nan'))\n        if self.anno_list is not None:\n            anno_file_path = self.anno_list[index]\n            anno_class_img = cv2.imread(anno_file_path, cv2.IMREAD_GRAYSCALE)  # [高さ][幅]\n\n        if self.transform is not None:\n            if self.anno_list is not None:\n                transformed = self.transform(image=img, mask=anno_class_img)\n                img, anno_class_img = transformed['image'], transformed['mask']\n            else:\n                transformed = self.transform(image=img)\n                img = transformed['image']\n        return img.float(), anno_class_img.float()","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.607424Z","iopub.execute_input":"2025-05-22T04:59:10.607633Z","iopub.status.idle":"2025-05-22T04:59:10.621261Z","shell.execute_reply.started":"2025-05-22T04:59:10.607602Z","shell.execute_reply":"2025-05-22T04:59:10.620565Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_img_list, val_img_list, train_anno_list, val_anno_list = train_test_split(img_list, anno_list)\n\ntrain_dataset = Dataset(train_img_list, train_anno_list, transform=train_transform)\nval_dataset = Dataset(val_img_list, val_anno_list, transform=val_transform)\n\nimg, anno_class_img = val_dataset[0]\nprint(img.shape)\nprint(anno_class_img.shape)\n\nimg_val = img.numpy().transpose((1, 2, 0))\nplt.imshow(img_val)\nplt.show()\n\nanno_class_img_val = anno_class_img.numpy()\nplt.imshow(anno_class_img_val)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:10.622068Z","iopub.execute_input":"2025-05-22T04:59:10.622245Z","iopub.status.idle":"2025-05-22T04:59:11.057447Z","shell.execute_reply.started":"2025-05-22T04:59:10.622223Z","shell.execute_reply":"2025-05-22T04:59:11.056775Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataloaderの作成","metadata":{}},{"cell_type":"code","source":"batch_size = 8\n\ntrain_dataloader = data.DataLoader(\n    train_dataset, batch_size=batch_size, shuffle=True)\n\nval_dataloader = data.DataLoader(\n    val_dataset, batch_size=batch_size, shuffle=False)","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:11.058533Z","iopub.execute_input":"2025-05-22T04:59:11.058800Z","iopub.status.idle":"2025-05-22T04:59:11.063692Z","shell.execute_reply.started":"2025-05-22T04:59:11.058765Z","shell.execute_reply":"2025-05-22T04:59:11.062963Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_iterator = iter(train_dataloader)\nimges, anno_class_imges = next(batch_iterator)\nprint(imges.size())\nprint(anno_class_imges.size())","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:11.064696Z","iopub.execute_input":"2025-05-22T04:59:11.065235Z","iopub.status.idle":"2025-05-22T04:59:11.704460Z","shell.execute_reply.started":"2025-05-22T04:59:11.065199Z","shell.execute_reply":"2025-05-22T04:59:11.703739Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# モデル定義","metadata":{"execution":{"iopub.status.busy":"2021-12-15T21:11:44.868506Z","iopub.execute_input":"2021-12-15T21:11:44.868832Z","iopub.status.idle":"2021-12-15T21:11:44.873295Z","shell.execute_reply.started":"2021-12-15T21:11:44.868801Z","shell.execute_reply":"2021-12-15T21:11:44.872651Z"}}},{"cell_type":"code","source":"model = smp.Unet(\n    encoder_name=encoder,\n    encoder_weights=\"imagenet\",\n    in_channels=3,\n    classes=class_num+1,\n)","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:11.705751Z","iopub.execute_input":"2025-05-22T04:59:11.706190Z","iopub.status.idle":"2025-05-22T04:59:12.090474Z","shell.execute_reply.started":"2025-05-22T04:59:11.706150Z","shell.execute_reply":"2025-05-22T04:59:12.089726Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 学習","metadata":{"execution":{"iopub.status.busy":"2021-12-15T21:20:14.348868Z","iopub.execute_input":"2021-12-15T21:20:14.349212Z","iopub.status.idle":"2021-12-15T21:20:14.354366Z","shell.execute_reply.started":"2021-12-15T21:20:14.349177Z","shell.execute_reply":"2021-12-15T21:20:14.353287Z"}}},{"cell_type":"code","source":"import torch\nimport torch.optim as optim\nimport torch.nn as nn\nfrom tqdm import tqdm\nimport numpy as np\n\n# --- User-defined Label Information (as before) ---\nlabel_info_dict = {\n    1: {'label': 'vehicle', 'color': [0, 0, 142]}, 2: {'label': 'pedestrian', 'color': [220, 20, 60]},\n    3: {'label': 'road', 'color': [128, 64, 128]}, 4: {'label': 'sidewalk', 'color': [244, 35, 232]},\n    5: {'label': 'bicycle', 'color': [119, 11, 32]}, 6: {'label': 'motorcycle', 'color': [0, 0, 230]},\n    7: {'label': 'building', 'color': [70, 70, 70]}, 8: {'label': 'terrain', 'color': [152, 251, 152]},\n    9: {'label': 'vegetation', 'color': [107, 142, 35]}, 10: {'label': 'pole', 'color': [153, 153, 153]},\n    11: {'label': 'traffic light', 'color': [250, 170, 30]}, 12: {'label': 'traffic sign', 'color': [220, 220, 0]}\n}\n\n# --- Helper Functions for F1 Score ---\ndef calculate_pixel_metrics_per_class(predictions, targets, num_classes, device):\n    # ... (same as before)\n    tp_per_class = torch.zeros(num_classes, device=device, dtype=torch.float)\n    fp_per_class = torch.zeros(num_classes, device=device, dtype=torch.float)\n    fn_per_class = torch.zeros(num_classes, device=device, dtype=torch.float)\n    for c_idx in range(num_classes):\n        pred_is_c = (predictions == c_idx)\n        target_is_c = (targets == c_idx)\n        tp_per_class[c_idx] += (pred_is_c & target_is_c).sum()\n        fp_per_class[c_idx] += (pred_is_c & ~target_is_c).sum()\n        fn_per_class[c_idx] += (~pred_is_c & target_is_c).sum()\n    return tp_per_class, fp_per_class, fn_per_class\n\ndef calculate_f1_from_tp_fp_fn(tp, fp, fn, epsilon=1e-7):\n    precision = tp / (tp + fp + epsilon)\n    recall = tp / (tp + fn + epsilon)\n    f1 = 2 * (precision * recall) / (precision + recall + epsilon)\n    f1 = torch.nan_to_num(f1, nan=0.0)\n    macro_f1 = f1.mean().item()\n    return f1, macro_f1\n\nNUM_CLASSES = 13\nprint(f\"Number of classes (including background if index 0 is used): {NUM_CLASSES}\")\n\nclass_index_to_name_map = [\"background\"]\nfor i in range(1, NUM_CLASSES):\n    if i in label_info_dict: class_index_to_name_map.append(label_info_dict[i]['label'])\n    else: class_index_to_name_map.append(f\"class_{i}\")\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"使用デバイス：\", device)\n\n# --- Calculate Class Weights ---\nprint(\"Calculating class weights from training data...\")\n# Ensure train_dataloader is defined and gives (images, annotation_masks)\n# It's important to iterate over the *entire* training dataset for accurate frequencies.\nclass_pixel_counts = torch.zeros(NUM_CLASSES, dtype=torch.float64, device='cpu') # Accumulate on CPU for potentially large datasets\ntotal_pixels_in_dataset = 0\n\ntemp_train_loader_for_weights = train_dataloader\n\nfor _, anno_class_imges_batch in tqdm(temp_train_loader_for_weights, desc=\"Counting class pixels\"):\n    anno_class_imges_batch = anno_class_imges_batch.to('cpu')\n    total_pixels_in_dataset += anno_class_imges_batch.numel()\n    for c_idx in range(NUM_CLASSES):\n        class_pixel_counts[c_idx] += (anno_class_imges_batch == c_idx).sum()\n\nprint(f\"Total pixels in dataset: {total_pixels_in_dataset}\")\nprint(f\"Pixel counts per class: {class_pixel_counts.tolist()}\")\n\n# Standard Inverse Class Frequency, normalized by total pixels\n# This is a common and effective method.\n# total_pixels = class_pixel_counts.sum() # This is total_pixels_in_dataset\nclass_frequencies = class_pixel_counts / total_pixels_in_dataset\nclass_weights = 1.0 / (class_frequencies + 1e-8) # Add epsilon for stability\nclass_weights = class_weights / class_weights.sum() * NUM_CLASSES\n\n\n# Ensure no NaN or Inf weights if a class had zero pixels (shouldn't happen for defined classes)\nclass_weights = torch.nan_to_num(class_weights, nan=1.0, posinf=1.0, neginf=1.0) # Replace NaN/inf with 1.0\n\nprint(f\"Calculated class weights: {class_weights.tolist()}\")\nclass_weights_tensor = class_weights.float().to(device) # Move to the target device\n\n# --- End Calculate Class Weights ---\n\nnum_epochs = 3 # Or your desired number of epochs\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\n\n\nmodel = model.to(device)\nbest_val_loss = float('inf')\nbest_val_macro_f1 = 0.0\n\n# --- Training Loop (largely the same, but criterion now has weights) ---\nfor epoch in range(num_epochs):\n    epoch_train_loss = 0.0\n    epoch_train_tp_total = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n    epoch_train_fp_total = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n    epoch_train_fn_total = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n\n    print('-------------')\n    print(f'epoch {epoch+1}/{num_epochs}')\n    print('-------------')\n\n    model.train()\n    for i, (imges, anno_class_imges) in enumerate(tqdm(train_dataloader, desc=f\"Epoch {epoch+1} Train\")):\n        imges = imges.to(device)\n        anno_class_imges = anno_class_imges.to(device)\n\n        outputs = model(imges)\n        loss = criterion(outputs, anno_class_imges.long()) # Criterion now uses weights\n\n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n\n        epoch_train_loss += loss.item()\n        _, predicted = torch.max(outputs.data, 1)\n\n        tp_batch, fp_batch, fn_batch = calculate_pixel_metrics_per_class(\n            predicted, anno_class_imges, NUM_CLASSES, device\n        )\n        epoch_train_tp_total += tp_batch\n        epoch_train_fp_total += fp_batch\n        epoch_train_fn_total += fn_batch\n\n        if (i + 1) % 75 == 0: # Or your desired print frequency\n            batch_f1_scores, batch_macro_f1 = calculate_f1_from_tp_fp_fn(\n                tp_batch, fp_batch, fn_batch\n            )\n            print(f'\\nBatch {i+1}/{len(train_dataloader)} || Train Loss: {loss.item():.4f}')\n            print(f\"  Train Batch Macro F1: {batch_macro_f1:.4f}\")\n            print(f\"  Train Batch Per-Label F1 Scores:\")\n            for class_idx, f1_val in enumerate(batch_f1_scores.tolist()):\n                label_name = class_index_to_name_map[class_idx] if class_idx < len(class_index_to_name_map) else f\"class_{class_idx}\"\n                print(f\"    {label_name}: {f1_val:.3f}\")\n\n            model.eval()\n            val_loss_total = 0.0\n            val_tp_accumulated = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n            val_fp_accumulated = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n            val_fn_accumulated = torch.zeros(NUM_CLASSES, device=device, dtype=torch.float)\n\n            with torch.no_grad():\n                for val_imges, val_anno in val_dataloader:\n                    val_imges = val_imges.to(device)\n                    val_anno = val_anno.to(device)\n                    outputs_val = model(val_imges)\n                    loss_val = criterion(outputs_val, val_anno.long()) # Criterion uses weights here too\n                    val_loss_total += loss_val.item()\n                    _, predicted_val = torch.max(outputs_val.data, 1)\n                    tp_v, fp_v, fn_v = calculate_pixel_metrics_per_class(\n                        predicted_val, val_anno, NUM_CLASSES, device\n                    )\n                    val_tp_accumulated += tp_v\n                    val_fp_accumulated += fp_v\n                    val_fn_accumulated += fn_v\n\n            avg_val_loss = val_loss_total / len(val_dataloader)\n            val_f1_per_class, val_macro_f1 = calculate_f1_from_tp_fp_fn(\n                val_tp_accumulated, val_fp_accumulated, val_fn_accumulated\n            )\n\n            print(f'Batch {i+1} Validation || Loss: {avg_val_loss:.4f} || Macro F1: {val_macro_f1:.4f}')\n            print(f\"  Validation Per-Label F1 Scores:\")\n            for class_idx, f1_val in enumerate(val_f1_per_class.tolist()):\n                label_name = class_index_to_name_map[class_idx] if class_idx < len(class_index_to_name_map) else f\"class_{class_idx}\"\n                print(f\"    {label_name}: {f1_val:.3f}\")\n\n            if val_macro_f1 > best_val_macro_f1:\n                best_val_macro_f1 = val_macro_f1\n                best_val_loss = avg_val_loss\n                torch.save(model.state_dict(), 'best_model_f1_weighted.pth') # Save with new name\n                print(f\"New best model saved with Macro F1: {best_val_macro_f1:.4f}!\")\n            model.train()\n\n    avg_epoch_train_loss = epoch_train_loss / len(train_dataloader)\n    epoch_train_f1_per_class, epoch_train_macro_f1 = calculate_f1_from_tp_fp_fn(\n        epoch_train_tp_total, epoch_train_fp_total, epoch_train_fn_total\n    )\n    print(f'\\nFinal Epoch {epoch+1} Training Summary:')\n    print(f'  Average Train Loss: {avg_epoch_train_loss:.4f}')\n    print(f'  Train Macro F1: {epoch_train_macro_f1:.4f}')\n    print(f\"  Train Per-Label F1 Scores:\")\n    for class_idx, f1_val in enumerate(epoch_train_f1_per_class.tolist()):\n        label_name = class_index_to_name_map[class_idx] if class_idx < len(class_index_to_name_map) else f\"class_{class_idx}\"\n        print(f\"    {label_name}: {f1_val:.3f}\")\n\nprint(\"\\nTraining Finished.\")\nprint(f\"Best Validation Macro F1: {best_val_macro_f1:.4f} (achieved with Loss: {best_val_loss:.4f})\")\nprint(\"Best model weights saved to 'best_model_f1_weighted.pth'\")","metadata":{"execution":{"iopub.status.busy":"2025-05-22T04:59:12.091891Z","iopub.execute_input":"2025-05-22T04:59:12.092119Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 推論","metadata":{}},{"cell_type":"code","source":"model.load_state_dict(torch.load('best_model_f1_weighted.pth'))\n\ntest_img_list, test_anno_list = sorted(glob(osp.join(test_dir, 'frames/*'))), sorted(glob(osp.join(test_dir, 'labels/*')))\ntest_dataset = Dataset(test_img_list, anno_list=None, transform=val_transform)\ntest_dataloader = data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# test\npreds = []\n\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\nmodel.eval()\nfor imges, anno_class_imges in tqdm(test_dataloader):\n    imges = imges.to(device)\n    anno_class_imges = anno_class_imges.to(device)\n\n    with torch.no_grad():\n        outputs = model(imges)\n        outputs = outputs.argmax(dim=1).detach().cpu().numpy().astype('uint8')\n    \n    preds += [outputs]\n    \npreds = np.concatenate(preds)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 提出ファイルの作成","metadata":{}},{"cell_type":"code","source":"# ランレングス符号へエンコードする関数\ndef rle_encode(img):\n    \"\"\" TBD\n    \n    Args:\n        img (np.array): \n            - 1 indicating mask\n            - 0 indicating background\n    \n    Returns: \n        run length as string formated\n    \"\"\"\n    \n    pixels = img.flatten()\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"result = pd.read_csv(osp.join(rootpath, 'sample_submission.csv'))\nencoded_pixels = []\nfor pred in tqdm(preds):\n    pred = cv2.resize(pred, dsize=img_size, interpolation=cv2.INTER_NEAREST)\n    for label_id in range(1, class_num+1):\n        rle = rle_encode(np.where(pred==label_id, 1, 0))\n        encoded_pixels += [rle]\nresult['EncodedPixels'] = encoded_pixels\nresult.to_csv('result.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}