{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":47317,"databundleVersionId":5799376,"sourceType":"competition"},{"sourceId":229464007,"sourceType":"kernelVersion"}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import warnings\nimport numpy as np\n\nwarnings.simplefilter('ignore')\n\nSEED = 333\nnp.random.seed(SEED)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:07.282063Z","iopub.execute_input":"2025-05-24T07:41:07.282657Z","iopub.status.idle":"2025-05-24T07:41:07.289013Z","shell.execute_reply.started":"2025-05-24T07:41:07.282623Z","shell.execute_reply":"2025-05-24T07:41:07.288458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\nvesuvius_path = '/kaggle/input/vesuvius-challenge-ink-detection/'\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:07.293669Z","iopub.execute_input":"2025-05-24T07:41:07.293860Z","iopub.status.idle":"2025-05-24T07:41:11.417768Z","shell.execute_reply.started":"2025-05-24T07:41:07.293844Z","shell.execute_reply":"2025-05-24T07:41:11.417169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom torch import Tensor\nimport cv2\n\nTIF_STARTS =  27\nTIF_RANGE = 10\n\ndef load_image_stack(ab_123,\n                     train_rectangle=None,\n                     test_train='train'):\n\n  surface_volume_filepath = vesuvius_path + \\\n  f'{test_train}/{ab_123}/surface_volume/'\n\n  tif_filepaths = \\\n    [surface_volume_filepath + tif_filepath \\\n     for tif_filepath in sorted(os.listdir(surface_volume_filepath))[:-4]]\n  tif_filepaths_stack = tif_filepaths[TIF_STARTS:TIF_STARTS + TIF_RANGE]\n\n  image_stack = []\n  for tif_filepath_stack in tif_filepaths_stack:\n    loaded_img = Tensor(cv2.imread(tif_filepath_stack,\n                                         0) / 65535.0).float() \\\n      .to(device)\n    if train_rectangle is None:\n      image_stack.append(loaded_img)\n    else:\n      image_stack.append(loaded_img[train_rectangle[1]:train_rectangle[1] + \\\n                                    train_rectangle[3],\n                                    train_rectangle[0]:train_rectangle[0] + \\\n                                    train_rectangle[2]])\n\n  return torch.stack(image_stack, dim=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.419189Z","iopub.execute_input":"2025-05-24T07:41:11.419522Z","iopub.status.idle":"2025-05-24T07:41:11.665393Z","shell.execute_reply.started":"2025-05-24T07:41:11.419502Z","shell.execute_reply":"2025-05-24T07:41:11.664815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_train_mask_label(train_123, train_rectangle=None):\n\n  mask_filepath = vesuvius_path + f'train/{train_123}/mask.png'\n  label_filepath = vesuvius_path + f'train/{train_123}/inklabels.png'\n\n  mask = cv2.imread(mask_filepath, 0) / 255.\n  label = cv2.imread(label_filepath, 0) / 255.\n  mask = mask[train_rectangle[1]:train_rectangle[1] + train_rectangle[3],\n              train_rectangle[0]:train_rectangle[0] + train_rectangle[2]]\n  label = label[train_rectangle[1]:train_rectangle[1] + train_rectangle[3],\n                train_rectangle[0]:train_rectangle[0] + train_rectangle[2]]\n\n  return mask, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.665987Z","iopub.execute_input":"2025-05-24T07:41:11.666172Z","iopub.status.idle":"2025-05-24T07:41:11.671095Z","shell.execute_reply.started":"2025-05-24T07:41:11.666156Z","shell.execute_reply":"2025-05-24T07:41:11.670265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGE_SIZE = 64\nRANGE = IMAGE_SIZE // 2\nSTRIDE = 17\n\ndef get_non_zero_indices(mask):\n  trim_margins = np.zeros(mask.shape, dtype=bool)\n  trim_margins[RANGE:mask.shape[0] - RANGE,\n             RANGE:mask.shape[1] - RANGE] = True\n\n  trim_margins_mask = np.array(mask) * trim_margins\n  del trim_margins\n\n  sparse_mask = np.zeros(mask.shape, dtype=bool)\n  sparse_mask[::STRIDE, ::STRIDE] = True\n\n  return np.argwhere(sparse_mask * trim_margins_mask)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.672625Z","iopub.execute_input":"2025-05-24T07:41:11.672852Z","iopub.status.idle":"2025-05-24T07:41:11.685846Z","shell.execute_reply.started":"2025-05-24T07:41:11.672827Z","shell.execute_reply":"2025-05-24T07:41:11.685146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nfrom torch.utils.data import Dataset\n\nclass SubvolumeDataset(Dataset):\n\n  def __init__(self, image_stack, label, non_zero_indices):\n    self.image_stack = image_stack\n    self.label = Tensor(label).float()\n    self.non_zero_indices = non_zero_indices\n\n  def __len__(self):\n    return len(self.non_zero_indices)\n\n  def __getitem__(self, idx):\n    y, x = self.non_zero_indices[idx]\n    subvolume = self.image_stack[:,\n                                 y - RANGE:y + RANGE,\n                                 x - RANGE:x + RANGE]\n    ink_label = self.label[y, x]\n\n    return subvolume, ink_label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.686543Z","iopub.execute_input":"2025-05-24T07:41:11.686753Z","iopub.status.idle":"2025-05-24T07:41:11.708816Z","shell.execute_reply.started":"2025-05-24T07:41:11.686728Z","shell.execute_reply":"2025-05-24T07:41:11.708086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.nn import Module, Sequential, Conv3d, ReLU, BatchNorm3d\n\nBATCH_NORM_MOMENTUM = 0.1\nFILTERS = [16, 32, 64, 128]\nFILTER_SIZES = [1] + FILTERS\nFILTER_PAIRS = list(zip(FILTER_SIZES[:-1], FILTER_SIZES[1:]))\nSTRIDES = [1, 2, 2, 2]\nKERNEL_SIZE = 3\nPADDING = 1\n\nclass Subvolume3DcnnEncoder(Module):\n\n  def __init__(self):\n\n    super().__init__()\n    self.conv_layers = Sequential(\n        *[Sequential(\n            Conv3d(chan_in,\n                   chan_out,\n                   kernel_size=KERNEL_SIZE,\n                   stride=stride, padding=PADDING),\n            ReLU(),\n            BatchNorm3d(num_features=filter_,\n                        momentum=BATCH_NORM_MOMENTUM)) \\\n          for (chan_in, chan_out), stride, filter_ in zip(FILTER_PAIRS,\n                                                          STRIDES,\n                                                          FILTERS)])\n    self.apply(self.init_weight)\n\n  @staticmethod\n  def init_weight(w):\n    if isinstance(w, Conv3d):\n      nn.init.xavier_uniform_(w.weight)\n      nn.init.zeros_(w.bias)\n\n  def forward(self, x):\n    return self.conv_layers(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.709650Z","iopub.execute_input":"2025-05-24T07:41:11.709945Z","iopub.status.idle":"2025-05-24T07:41:11.725559Z","shell.execute_reply.started":"2025-05-24T07:41:11.709919Z","shell.execute_reply":"2025-05-24T07:41:11.724909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.nn import Linear, Flatten, Sigmoid\n\nclass LinearInkDecoder(nn.Module):\n\n  def __init__(self, input_shape):\n\n    super().__init__()\n    self.linear = Linear(int(np.prod(input_shape)), 1)\n    self.flatten = Flatten()\n    self.sigmoid = Sigmoid()\n\n  def forward(self, x):\n    return self.sigmoid(self.linear(self.flatten(x)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.726332Z","iopub.execute_input":"2025-05-24T07:41:11.726587Z","iopub.status.idle":"2025-05-24T07:41:11.742722Z","shell.execute_reply.started":"2025-05-24T07:41:11.726570Z","shell.execute_reply":"2025-05-24T07:41:11.741981Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SUBVOLUME_SHAPE = [IMAGE_SIZE, IMAGE_SIZE, 10]\n\nclass InkClassifier3DCNN(nn.Module):\n\n  def __init__(self):\n    super().__init__()\n    self.encoder = Subvolume3DcnnEncoder()\n    self.decoder = LinearInkDecoder(\n        self.encoder(torch.zeros((1,\n                                  1,\n                                  *SUBVOLUME_SHAPE))).shape[1:])\n  def forward(self, x):\n    return self.decoder(self.encoder(x))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.743523Z","iopub.execute_input":"2025-05-24T07:41:11.743801Z","iopub.status.idle":"2025-05-24T07:41:11.755506Z","shell.execute_reply.started":"2025-05-24T07:41:11.743777Z","shell.execute_reply":"2025-05-24T07:41:11.754848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# torch.serialization.add_safe_globals([InkClassifier3DCNN])\n# torch.serialization.safe_globals([InkClassifier3DCNN])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.756152Z","iopub.execute_input":"2025-05-24T07:41:11.756429Z","iopub.status.idle":"2025-05-24T07:41:11.768253Z","shell.execute_reply.started":"2025-05-24T07:41:11.756405Z","shell.execute_reply":"2025-05-24T07:41:11.767708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\nBATCH_SIZE = 32\ndef generate_predictions(ab_123='a', train_rectangle=None):\n\n  if ab_123 in ['a', 'b']:\n    mask_filepath = vesuvius_path + f'test/{ab_123}/mask.png'\n    mask = Tensor(cv2.imread(mask_filepath, 0) / 255.)\n    image_stack = load_image_stack(ab_123, test_train='test')\n  elif ab_123 in [1, 2, 3]:\n    mask, label = load_train_mask_label(ab_123, train_rectangle=train_rectangle)\n    image_stack = load_image_stack(ab_123, train_rectangle=train_rectangle)\n\n  test_non_zero_indices = get_non_zero_indices(mask)\n\n  test_dataset = SubvolumeDataset(image_stack, mask, test_non_zero_indices)\n  test_dataloader = DataLoader(test_dataset,\n                               batch_size=BATCH_SIZE,\n                               shuffle=False)\n\n  output = torch.zeros_like(torch.Tensor(mask)).float()\n  model = torch.load('/kaggle/input/vesuvius-challenge-train/vc_train_s_333_tts_14000_sts_4000_v4.pt',\n                    weights_only=False)\n  model.eval()\n\n  with torch.no_grad():\n    for i, (subvolumes, _) in enumerate(test_dataloader):\n      for j, value in enumerate(\n          model(subvolumes.to(device).permute(0, 2, 3, 1).unsqueeze(dim=1))):\n          y, x = test_non_zero_indices[i * BATCH_SIZE + j]\n          output[y - RANGE:y + RANGE + 1,\n                 x - RANGE:x + RANGE + 1] = value\n\n  return output.cpu()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.770438Z","iopub.execute_input":"2025-05-24T07:41:11.770657Z","iopub.status.idle":"2025-05-24T07:41:11.783593Z","shell.execute_reply.started":"2025-05-24T07:41:11.770626Z","shell.execute_reply":"2025-05-24T07:41:11.782829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_RECTANGLE = [2000, 400, 2500, 1000]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.784329Z","iopub.execute_input":"2025-05-24T07:41:11.784576Z","iopub.status.idle":"2025-05-24T07:41:11.801052Z","shell.execute_reply.started":"2025-05-24T07:41:11.784548Z","shell.execute_reply":"2025-05-24T07:41:11.800355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ntrain_123 = 1\ntrain_pred = generate_predictions(ab_123=train_123,\n                                  train_rectangle=TRAIN_RECTANGLE)\nmask, label = load_train_mask_label(train_123, train_rectangle=TRAIN_RECTANGLE)\nplt.imshow(train_pred, cmap='gray')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:11.801771Z","iopub.execute_input":"2025-05-24T07:41:11.802009Z","iopub.status.idle":"2025-05-24T07:41:34.583901Z","shell.execute_reply.started":"2025-05-24T07:41:11.801988Z","shell.execute_reply":"2025-05-24T07:41:34.583206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BETA_SQUARED = 0.5 * 0.5\nSMOOTH = 1e-5\n\ndef dice_coef_torch(preds, label):\n  preds = np.array(preds)\n  label = np.array(label)\n  y_true_count = label.sum()\n  preds_true_count = preds[label == 1].sum()\n  preds_false_count = preds[label == 0].sum()\n\n  c_precision = preds_true_count / (preds_true_count + preds_false_count + SMOOTH)\n  c_recall = preds_true_count / (y_true_count + SMOOTH)\n  dice = (1 + BETA_SQUARED) * (c_precision * c_recall) / \\\n    (BETA_SQUARED * c_precision + c_recall + SMOOTH)\n\n  return round(dice, 6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:34.584729Z","iopub.execute_input":"2025-05-24T07:41:34.584938Z","iopub.status.idle":"2025-05-24T07:41:34.589854Z","shell.execute_reply.started":"2025-05-24T07:41:34.584921Z","shell.execute_reply":"2025-05-24T07:41:34.589134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SAMPLES_TO_TEST = 200\nbest_dice = 0\nbest_threshold = 0\n\nfor threshold in torch.rand(SAMPLES_TO_TEST):\n  binary_pred = train_pred.clone().gt(threshold)\n  dice_coef = dice_coef_torch(binary_pred, label)\n\n  if dice_coef > best_dice:\n    best_dice = dice_coef\n    best_threshold = threshold\n\nbinary_pred = train_pred.clone().gt(best_threshold)\nprint(f'BEST_THRESHOLD: {best_threshold}')\nplt.imshow(binary_pred, cmap='gray')\n# plt.imshow(label, cmap='gray', alpha=0.5)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:34.590505Z","iopub.execute_input":"2025-05-24T07:41:34.590680Z","iopub.status.idle":"2025-05-24T07:41:38.624685Z","shell.execute_reply.started":"2025-05-24T07:41:34.590667Z","shell.execute_reply":"2025-05-24T07:41:38.624044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.imshow(label, cmap='gray', alpha=0.5)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:38.625388Z","iopub.execute_input":"2025-05-24T07:41:38.625636Z","iopub.status.idle":"2025-05-24T07:41:38.895451Z","shell.execute_reply.started":"2025-05-24T07:41:38.625608Z","shell.execute_reply":"2025-05-24T07:41:38.894784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nprint(f'GC.COLLECT(): {gc.collect()}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:38.896160Z","iopub.execute_input":"2025-05-24T07:41:38.896410Z","iopub.status.idle":"2025-05-24T07:41:38.987601Z","shell.execute_reply.started":"2025-05-24T07:41:38.896393Z","shell.execute_reply":"2025-05-24T07:41:38.986917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_length_encoding(img, threshold=best_threshold):\n    img = np.array(img)\n\n    flat_img = img.flatten()\n    flat_img = np.where(flat_img > threshold, 1, 0).astype(np.uint8)\n\n    starts = np.array((flat_img[:-1] == 0) & (flat_img[1:] == 1))\n    ends = np.array((flat_img[:-1] == 1) & (flat_img[1:] == 0))\n    starts_ids = np.where(starts)[0] + 2\n    ends_ids = np.where(ends)[0] + 2\n    lengths = ends_ids - starts_ids\n\n    return starts_ids, lengths","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:38.988483Z","iopub.execute_input":"2025-05-24T07:41:38.989137Z","iopub.status.idle":"2025-05-24T07:41:39.002507Z","shell.execute_reply.started":"2025-05-24T07:41:38.989110Z","shell.execute_reply":"2025-05-24T07:41:39.001805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_threshold","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:39.003237Z","iopub.execute_input":"2025-05-24T07:41:39.003446Z","iopub.status.idle":"2025-05-24T07:41:39.036758Z","shell.execute_reply.started":"2025-05-24T07:41:39.003431Z","shell.execute_reply":"2025-05-24T07:41:39.036226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# best_threshold += best_threshold / 10\nbest_threshold *= 1.2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:39.037436Z","iopub.execute_input":"2025-05-24T07:41:39.037681Z","iopub.status.idle":"2025-05-24T07:41:39.041817Z","shell.execute_reply.started":"2025-05-24T07:41:39.037659Z","shell.execute_reply":"2025-05-24T07:41:39.041173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_list = []\n\nfor test_ab in ['a', 'b']:\n  test_pred = generate_predictions(ab_123=test_ab)\n  plt.imshow(test_pred.gt(best_threshold), cmap='gray')\n  plt.show()\n\n  starts_ids, lengths = run_length_encoding(test_pred, threshold=best_threshold.item())\n  inklabels_rle = ' '.join(map(str, sum(zip(starts_ids, lengths), ())))\n  pred_list.append({'Id': str(test_ab).split('/')[-1], 'Predicted': inklabels_rle})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:41:39.042597Z","iopub.execute_input":"2025-05-24T07:41:39.042826Z","iopub.status.idle":"2025-05-24T07:42:25.328059Z","shell.execute_reply.started":"2025-05-24T07:41:39.042803Z","shell.execute_reply":"2025-05-24T07:42:25.327532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.DataFrame(pred_list)\ndf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:42:25.328728Z","iopub.execute_input":"2025-05-24T07:42:25.328932Z","iopub.status.idle":"2025-05-24T07:42:25.606061Z","shell.execute_reply.started":"2025-05-24T07:42:25.328917Z","shell.execute_reply":"2025-05-24T07:42:25.605418Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.to_csv(f'submission_bt_s_333_{best_threshold:0.4f}_tts_14000_sts_4000_v4.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:42:25.606772Z","iopub.execute_input":"2025-05-24T07:42:25.607126Z","iopub.status.idle":"2025-05-24T07:42:25.627286Z","shell.execute_reply.started":"2025-05-24T07:42:25.607108Z","shell.execute_reply":"2025-05-24T07:42:25.626786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T07:42:25.627949Z","iopub.execute_input":"2025-05-24T07:42:25.628176Z","iopub.status.idle":"2025-05-24T07:42:25.642401Z","shell.execute_reply.started":"2025-05-24T07:42:25.628160Z","shell.execute_reply":"2025-05-24T07:42:25.641687Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}