{"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":"none","dataSources":[{"sourceId":45867,"databundleVersionId":6924515,"sourceType":"competition"},{"sourceId":251265663,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Install Dependencies","metadata":{}},{"cell_type":"code","source":"# Installs the C library without showing output\n!apt-get update &> /dev/null && apt-get install -y libvips &> /dev/null\n# Installs the Python wrapper without showing output\n!pip install -q --no-cache-dir pyvips &> /dev/null","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T20:48:25.991717Z","iopub.execute_input":"2025-07-18T20:48:25.992075Z","iopub.status.idle":"2025-07-18T20:49:50.399115Z","shell.execute_reply.started":"2025-07-18T20:48:25.992053Z","shell.execute_reply":"2025-07-18T20:49:50.397375Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Fast Patch Selection Algorithm","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\n# from openslide import OpenSlide\nimport openslide\nfrom pathlib import Path\nimport glob\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom multiprocessing import Pool\nfrom tqdm import tqdm\nimport os\nimport pandas as pd\nimport random\nfrom sklearn.neighbors import KernelDensity\nfrom PIL import Image\n\ndef get_sampled_points_density_proportional_KDE(points, desired_sample_size):\n    num_points = len(points)\n    if num_points <= desired_sample_size:\n        return points\n\n    points_arr = np.array(points)\n    \n    # Fit KDE model to the points\n    kde = KernelDensity(bandwidth=0.1)  # You can adjust the bandwidth\n    kde.fit(points_arr)\n\n    # Generate samples from the KDE\n    samples = kde.sample(desired_sample_size)\n    final_sample = samples.tolist()\n\n    return final_sample\n\n\ndef RGB2HSD(X):\n    eps = np.finfo(float).eps\n    X[np.where(X==0.0)] = eps\n    \n    OD = -np.log(X / 1.0)\n    D  = np.mean(OD,3)\n    D[np.where(D==0.0)] = eps\n    \n    cx = OD[:,:,:,0] / (D) - 1.0\n    cy = (OD[:,:,:,1]-OD[:,:,:,2]) / (np.sqrt(3.0)*D)\n    \n    D = np.expand_dims(D,3)\n    cx = np.expand_dims(cx,3)\n    cy = np.expand_dims(cy,3)\n            \n    X_HSD = np.concatenate((D,cx,cy),3)\n    return X_HSD\n\n\ndef clean_thumbnail(thumbnail):\n    thumbnail_arr = np.asarray(thumbnail)\n    \n    wthumbnail = np.zeros_like(thumbnail_arr)\n    wthumbnail[:, :, :] = thumbnail_arr[:, :, :]\n\n    thumbnail_std = np.std(wthumbnail, axis=2)\n    wthumbnail[thumbnail_std<5] = (np.ones((1,3), dtype=\"uint8\")*255)\n    thumbnail_HSD = RGB2HSD( np.array([wthumbnail.astype('float32')/255.]) )[0]\n    kernel = np.ones((30,30),np.float32)/900\n    thumbnail_HSD_mean = cv2.filter2D(thumbnail_HSD[:,:,2],-1,kernel)\n    wthumbnail[thumbnail_HSD_mean<0.05] = (np.ones((1,3),dtype=\"uint8\")*255)\n    return wthumbnail\n\n                \ndef is_far_enough(new_point, existing_points, min_distance):\n    for point in existing_points:\n        if np.sqrt((new_point[0] - point[0])**2 + (new_point[1] - point[1])**2) < min_distance:\n            return False\n    return True\n\n\ndef get_patch_locations(tissue_mask, cthumbnail,  mask_hratio, mask_wratio, tissue_threshold, stride):\n    contours, mm = cv2.findContours(tissue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    image_with_contours = cthumbnail.copy()\n    cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 2)  # Draw contours on the image\n    \n    image_with_rectangles = cthumbnail.copy()\n    \n    # Calculate the step size for the grid based on the stride\n    step_w = int(mask_wratio * stride)\n    step_h = int(mask_hratio * stride)\n    \n    patch_locations = []\n    for contour in contours:\n        x, y, w, h = cv2.boundingRect(contour)\n        # plot the rectangles on the image_with_rectangles\n        cv2.rectangle(image_with_rectangles, (x, y), (x + w, y + h), (0, 255, 0), 2)\n        \n        if w >= mask_wratio and h >= mask_hratio:\n            for i in range(x, x + w - mask_wratio, step_w):\n                for j in range(y, y + h - mask_hratio, step_h):\n                    tissue_patch = tissue_mask[j:j + mask_hratio, i:i + mask_wratio]\n                    # if np.sum(tissue_patch) / (mask_hratio ** 2) > tissue_threshold:\n                    tissue_magnitude = np.count_nonzero(tissue_patch)/tissue_patch.size\n                    if tissue_magnitude  >= tissue_threshold:\n                        patch_locations.append(((i, j),tissue_magnitude))\n\n    return patch_locations, image_with_contours, image_with_rectangles\n\ndef process_wsi(wsi_obj, wsi_path, thumbnail_path, is_tma, output_patch_size=1000, tissue_percent=0.9, returnSamples=30, stride=1):\n    wsi_name = Path(wsi_path).stem + \".svs\"\n\n    if is_tma:\n        thumbnail = Image.open(wsi_path)\n        objective_power = 40\n    else:\n        thumbnail = Image.open(thumbnail_path)\n        objective_power = 20\n    \n    cthumbnail = clean_thumbnail(thumbnail)\n    tissue_mask = ((cthumbnail.mean(axis=2) != 255) * 255).astype(np.uint8)\n    # print(f\"the shape of tissue_mask is {tissue_mask.shape}\")\n    \n    # try:\n    #     objective_power = int(wsi_obj.properties['openslide.objective-power'])\n    # except:\n    #     objective_power = 20\n         \n    w, h = wsi_obj.dimensions\n    mask_hratio = int((tissue_mask.shape[0] / h) * output_patch_size)\n    mask_wratio = int((tissue_mask.shape[1] / w) * output_patch_size)\n    # Ensure the step size is at least 1 pixel\n    if mask_hratio == 0:\n        mask_hratio = 1\n    if mask_wratio == 0:\n        mask_wratio = 1\n    # print(f\"mask_hratio is {mask_hratio} and mask_wratio is {mask_wratio}\")\n    # estimate the mask patch size given the size of the WSI, the size of the mask, and the output patch size\n    mask_patch_size = int(output_patch_size / mask_wratio)\n    \n    Mask_to_WSI_ratioW = int(w / tissue_mask.shape[1])\n    Mask_to_WSI_ratioH = int(h / tissue_mask.shape[0])\n    \n    patch_locations, image_with_contours, image_with_rectangles = get_patch_locations(tissue_mask, cthumbnail, mask_hratio, mask_wratio, tissue_percent, stride)\n    # print(f\"initially generated {len(patch_locations)} patch locations\")\n    min_distance = mask_hratio * 2  # Minimum distance between points\n\n    filtered_patch_locations = []\n    for (x, y), _ in patch_locations:\n        if is_far_enough((x, y), filtered_patch_locations, min_distance):\n            filtered_patch_locations.append((x, y))\n\n    # print(f\"after is_far_enough there are {len(filtered_patch_locations)} patch locations\")\n    filtered_patch_locations = get_sampled_points_density_proportional_KDE(filtered_patch_locations, returnSamples)\n\n    scaled_patch_coordinates = []\n    for (x, y) in filtered_patch_locations:\n        scaled_patch_coordinates.append((int(x * Mask_to_WSI_ratioW), int(y * Mask_to_WSI_ratioH)))\n\n    return scaled_patch_coordinates\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T20:24:58.296652Z","iopub.execute_input":"2025-07-18T20:24:58.297342Z","iopub.status.idle":"2025-07-18T20:25:00.603898Z","shell.execute_reply.started":"2025-07-18T20:24:58.297281Z","shell.execute_reply":"2025-07-18T20:25:00.602665Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Import, Patch Selection, and Tensor Computation","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torchvision.transforms as T\nimport openslide\nimport pyvips\n\nclass SlidePatchExtractor:\n    def __init__(self, image_id, patch_size=224, mode='train', tissue_threshold=0.9, num_patches=100):\n        \n        self.image_id = image_id\n        self.patch_size = patch_size\n        self.mode = mode\n        self.transform = T.Compose([\n            T.ToTensor(),\n            T.Resize((self.patch_size, self.patch_size), antialias=True),\n            # T.Normalize(mean=[0.2585, 0.2556, 0.2506], std=[0.229, 0.224, 0.225])\n            T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n        self.tissue_threshold = tissue_threshold\n        self.num_patches = num_patches\n        \n        # self.train_transform = T.Compose([\n        #     T.RandomHorizontalFlip(p=0.5),\n        #     T.RandomVerticalFlip(p=0.5),\n        #     T.RandomRotation(degrees=45),\n        #     T.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.1),\n        #     T.ToTensor(),\n        #     T.Resize((224, 224), antialias=True),\n        #     T.Normalize(mean=[0.2585, 0.2556, 0.2506], std=[0.229, 0.224, 0.225])\n        # ])\n\n        # Define paths for the source WSI and its thumbnail\n        self.source_path = os.path.join('/kaggle/input/UBC-OCEAN', f'{self.mode}_images', self.image_id + '.png')\n        self.thumbnail_path = os.path.join('/kaggle/input/UBC-OCEAN', f'{self.mode}_thumbnails', self.image_id + '_thumbnail.png')\n        \n        try:\n            self.slide = openslide.open_slide(self.source_path)\n        except openslide.OpenSlideError as e:\n            print(f\"Could not open slide {self.source_path}: {e}\")\n            self.patch_locations = []\n            return\n\n        self.width, self.height = self.slide.dimensions\n        self.is_tma = self.width < 5000 and self.height < 5000\n        standard_magnification = 20\n        self.objective_power = 40 if self.is_tma else 20\n        magnification_factor = self.objective_power / standard_magnification\n        self.extraction_patch_size = int(self.patch_size * magnification_factor)\n        \n        self.stride = 1 if self.is_tma else 4\n        \n        self.patch_locations = process_wsi(\n            wsi_obj=self.slide,\n            wsi_path=self.source_path,\n            thumbnail_path=self.thumbnail_path,\n            is_tma=self.is_tma,\n            output_patch_size=patch_size,\n            tissue_percent=tissue_threshold,\n            returnSamples=num_patches,\n            stride=self.stride\n        )\n    \n    def __len__(self):\n        \"\"\"Returns the number of patches found for this slide.\"\"\"\n        return len(self.patch_locations)\n    \n    def get_all_patch_tensors(self):\n        \"\"\"\n        Extracts all patches from the slide and returns them as a stacked tensor.\n        \"\"\"\n        patch_tensors = []\n        flat_feature_size = 3 * self.patch_size * self.patch_size\n        self.slide = pyvips.Image.new_from_file(self.source_path)\n        if not self.patch_locations:\n            # If no patches were found, return an empty tensor with the correct shape\n            return torch.empty((0, flat_feature_size))\n\n        for (x, y) in self.patch_locations:\n            try:\n                # patch_image = self.slide.read_region(\n                #     (x, y), 0, (self.extraction_patch_size, self.extraction_patch_size)\n                # ).convert('RGB')\n                patch_image = self.slide.crop(x, y, self.patch_size, self.patch_size).numpy()[..., :3]\n                patch_tensor = self.transform(patch_image)\n                patch_tensors.append(patch_tensor)\n            except Exception as e:\n                print(f\"Error reading patch at ({x},{y}) for slide {self.image_id}: {e}\")\n                continue\n        \n        if not patch_tensors:\n            return torch.empty((0, flat_feature_size))\n            \n        # Stack all patch tensors into a single 4D tensor (num_patches, 3, H, W)\n        stacked_patches = torch.stack(patch_tensors)\n        # Flatten the patch dimensions (3, H, W) into a single vector for each patch\n        flattened_patches = torch.flatten(stacked_patches, start_dim=1)\n        \n        return flattened_patches\n    \n    def get_patch(self, idx):\n        x, y = self.patch_locations[idx]\n        self.slide = pyvips.Image.new_from_file(self.source_path)\n        patch_image = self.slide.crop(x, y, self.patch_size, self.patch_size).numpy()[..., :3]\n        # patch_image = self.slide.read_region((x, y), 0, (self.extraction_patch_size, self.extraction_patch_size)).convert('RGB')\n        patch_tensor = self.transform(patch_image)\n        return patch_tensor, patch_image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:19:52.761586Z","iopub.execute_input":"2025-07-18T21:19:52.762425Z","iopub.status.idle":"2025-07-18T21:19:52.785721Z","shell.execute_reply.started":"2025-07-18T21:19:52.762377Z","shell.execute_reply":"2025-07-18T21:19:52.784469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport openslide\nfrom PIL import Image\nimport numpy as np\nfrom tqdm import tqdm\n\nclass UBCDataset(Dataset):\n    \"\"\"\n    The main Dataset class for loading slides and their labels.\n    \"\"\"\n    def __init__(self, dataframe, label_map, mode='train', patch_size=224, tissue_threshold=0.9, num_patches=100):\n        self.df = dataframe\n        self.label_map = label_map\n        self.mode = mode\n        self.patch_size = patch_size\n        self.tissue_threshold = tissue_threshold\n        self.num_patches = num_patches\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image_id = str(row['image_id'])\n        \n        string_label = row['label']\n        # Use the label map to convert the string label to an integer\n        int_label = self.label_map[string_label]\n        # Create the tensor from the integer\n        label = torch.tensor(int_label, dtype=torch.long)\n\n        extractor = SlidePatchExtractor(\n            image_id=image_id,\n            mode=self.mode,\n            patch_size=self.patch_size,\n            tissue_threshold=self.tissue_threshold,\n            num_patches=self.num_patches\n        )\n        \n        patch_tensors = extractor.get_all_patch_tensors()\n\n        return {\"patches\": patch_tensors, \"label\": label, \"image_id\": image_id}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:19:57.791536Z","iopub.execute_input":"2025-07-18T21:19:57.792965Z","iopub.status.idle":"2025-07-18T21:19:57.809946Z","shell.execute_reply.started":"2025-07-18T21:19:57.792882Z","shell.execute_reply":"2025-07-18T21:19:57.807854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Collate Function for the DataLoader ---\ndef collate_fn(batch):\n    patches_list = [item['patches'] for item in batch]\n    labels = torch.stack([item['label'] for item in batch])\n    image_ids = [item['image_id'] for item in batch]\n\n    return {\"patches\": patches_list, \"labels\": labels, \"image_ids\": image_ids}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:19:59.495007Z","iopub.execute_input":"2025-07-18T21:19:59.495535Z","iopub.status.idle":"2025-07-18T21:19:59.503947Z","shell.execute_reply.started":"2025-07-18T21:19:59.495502Z","shell.execute_reply":"2025-07-18T21:19:59.50225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from transformers import AutoModel\nfrom torchvision import transforms\nfrom PIL import Image\nimport numpy as np\nimport h5py\nimport os\nfrom typing import List, Union, Optional, Tuple\n\nclass PhikonFeatureExtractor:\n    def __init__(self, model_name: str = \"owkin/phikon\", batch_size: int = 16, device: str = \"cuda\", apply_normalization: bool = True):\n        \"\"\"\n        Initialize the Phikon feature extractor.\n        \"\"\"\n        self.device = torch.device(device if torch.cuda.is_available() else \"cpu\")\n        self.batch_size = batch_size\n        self.apply_normalization = apply_normalization\n        \n        # Load Phikon model (teacher backbone)\n        self.model = AutoModel.from_pretrained(model_name, trust_remote_code=True)\n        self.model.eval()  # Set to evaluation mode\n        self.model.to(self.device)\n        \n        # Define preprocessing transform for PIL images\n        self.transform = transforms.Compose([\n            transforms.Resize((224, 224)),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) if apply_normalization else transforms.Lambda(lambda x: x),\n        ])\n        \n    def preprocess_tensor(self, tensor: torch.Tensor) -> torch.Tensor:\n        \"\"\"\n        Args:\n            tensor (torch.Tensor): Input tensor of shape (N, 3, 224, 224).\n        \n        Returns:\n            torch.Tensor: Processed tensor, normalized if apply_normalization is True.\n        \"\"\"   \n        if tensor.max() > 1.0:\n            tensor = tensor / 255.0  # Scale if in [0, 255]\n        \n        if self.apply_normalization:\n            normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            tensor = normalize(tensor)\n        \n        return tensor.to(self.device)\n    \n    def extract_features_from_tensor(self, tensor: torch.Tensor) -> Tuple[np.ndarray]:\n        \"\"\"\n        Extract features from a preprocessed tensor of shape (N, 3, 224, 224).\n        \n        Args:\n            tensor (torch.Tensor): Input tensor of shape (N, 3, 224, 224).\n        \n        Returns:\n            np.ndarray: Features (N, 768).\n        \"\"\"\n        features_list = []\n        num_images = tensor.shape[0]\n        \n        # Preprocess tensor\n        tensor = self.preprocess_tensor(tensor)\n        \n        for i in range(0, num_images, self.batch_size):\n            batch_tensor = tensor[i:i + self.batch_size]\n            \n            with torch.no_grad():\n                outputs = self.model(batch_tensor)\n                batch_features = outputs.last_hidden_state[:, 0, :]  # CLS token\n                \n                features_list.append(batch_features.cpu().numpy())\n        \n        features = np.concatenate(features_list, axis=0)\n        \n        return features","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Main Training Block","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom torch.utils.data import Dataset, DataLoader\nimport torch\nimport gzip\n\n# Disable the Decompression Bomb check\nImage.MAX_IMAGE_PIXELS = None\n\n# Hyperparameters\nbatch_size = 4\npatch_size = 224\nnum_patches = 256\ntissue_threshold = 0.9\n\nbase_path = '/kaggle/input/UBC-OCEAN'\ntrain_df = pd.read_csv(os.path.join(base_path, 'train.csv'))\n\n# Create a mapping from string labels to integers\nunique_labels = sorted(train_df['label'].unique())\nlabel_to_int = {label: i for i, label in enumerate(unique_labels)}\nint_to_label = {i: label for label, i in label_to_int.items()}\n\n# testing the code, only take the top 20 reocrds in training file\ntrain_df = train_df.head(20)\n\n# create the dataset using PyTorch Dataset\nubc_dataset = UBCDataset(\n    dataframe=train_df,\n    label_map=label_to_int,\n    patch_size=patch_size,\n    tissue_threshold=tissue_threshold,\n    num_patches=num_patches\n)\n\n# create the training data loader, potentially change shuffle and num_workers\ntrain_loader = DataLoader(\n    ubc_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n)\n\n# all_patch_tensors = {}\nfeature_extractor = PhikonFeatureExtractor(batch_size=16, device=\"cuda\", apply_normalization=False)\n\nall_features = []\nall_aggregated_features = []\n\nfor i, batch in enumerate(tqdm(train_loader, desc=\"Processing Batches\")):\n    print(f\"\\n--- Batch {i+1} ---\")\n    \n    patches_list = batch['patches']\n    labels = batch['labels']\n    image_ids = batch['image_ids']\n    \n    print(f\"Number of slides in this batch: {len(patches_list)}\")\n    print(f\"Labels for this batch (as integers): {labels.numpy()}\")\n    print(f\"Image IDs for this batch: {image_ids}\")\n\n    for slide_idx in range(len(image_ids)):\n        slide_id = image_ids[slide_idx]\n        slide_patches = patches_list[slide_idx]\n        slide_label_int = labels[slide_idx].item()\n\n        slide_patches4feature = slide_patches.view(-1, 3, patch_size, patch_size)\n        features = feature_extractor.extract_features_from_tensor(slide_patches4feature)\n        # Aggregate features per slide (mean pooling):\n        aggregated_features = np.mean(features, axis=0, keepdims=True)\n        \n        all_features.append(features)\n        all_aggregated_features.append(aggregated_features)\n\n        file_name = slide_id + '_patches.pt.gz'\n        # torch.save(slide_patches, file_name)\n        with gzip.open(file_name, 'wb') as f:\n            torch.save(slide_patches, f)\n        \n        print(f\"  - Slide ID: {slide_id}, Label: {int_to_label[slide_label_int]} ({slide_label_int}), Patches: {slide_patches.shape[0]}\")\n        \n        # all_patch_tensors[slide_id] = slide_patches\n\n# print(f\"\\n--- Finished processing. Total slides with stored tensors: {len(all_patch_tensors)} ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T20:30:03.027805Z","iopub.execute_input":"2025-07-18T20:30:03.028332Z","iopub.status.idle":"2025-07-18T20:35:38.612068Z","shell.execute_reply.started":"2025-07-18T20:30:03.028129Z","shell.execute_reply":"2025-07-18T20:35:38.61032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import gzip\n\n# # torch.save(slide_patches, 'slide_patches.pt')\n# with gzip.open('slide_patches.pt.gz', 'wb') as f:\n#     torch.save(slide_patches, f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T20:40:40.119017Z","iopub.execute_input":"2025-07-18T20:40:40.119547Z","iopub.status.idle":"2025-07-18T20:41:05.469494Z","shell.execute_reply.started":"2025-07-18T20:40:40.119515Z","shell.execute_reply":"2025-07-18T20:41:05.468392Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !rm /kaggle/working/*.gz","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:28:19.378982Z","iopub.execute_input":"2025-07-18T21:28:19.379536Z","iopub.status.idle":"2025-07-18T21:28:19.922375Z","shell.execute_reply.started":"2025-07-18T21:28:19.379492Z","shell.execute_reply":"2025-07-18T21:28:19.920367Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Store Patch Tensors","metadata":{}},{"cell_type":"code","source":"# from joblib import dump\n\n# dump(all_patch_tensors, 'all_patch_tensors.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T01:53:25.084938Z","iopub.execute_input":"2025-07-18T01:53:25.085614Z","iopub.status.idle":"2025-07-18T01:53:28.577703Z","shell.execute_reply.started":"2025-07-18T01:53:25.08557Z","shell.execute_reply":"2025-07-18T01:53:28.575985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from joblib import load\n# import os\n\n# input_path = '/kaggle/input/ovarian-caner-subtype-classification'\n# file_path = os.path.join(input_path, 'all_patch_tensors.joblib')\n# new_dict = load(file_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T02:10:56.783656Z","iopub.execute_input":"2025-07-18T02:10:56.784211Z","iopub.status.idle":"2025-07-18T02:11:10.973824Z","shell.execute_reply.started":"2025-07-18T02:10:56.784174Z","shell.execute_reply":"2025-07-18T02:11:10.972444Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test the data class","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\n# Hyperparameters\nnum_patches = 512\ntissue_threshold = 0.9\n\n# Disable the Decompression Bomb check\nImage.MAX_IMAGE_PIXELS = None\n\nbase_path = '/kaggle/input/UBC-OCEAN'\ntrain_labels_df = pd.read_csv(os.path.join(base_path, 'train.csv'))\n# image_id = str(train_labels_df.loc[2, 'image_id'])\nimage_id = str(4)\nwsi = SlidePatchExtractor(image_id=image_id, tissue_threshold=tissue_threshold, num_patches=num_patches)\nprint(f'The image is {wsi.width} width and {wsi.height} height')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:13:23.731843Z","iopub.execute_input":"2025-07-18T21:13:23.734199Z","iopub.status.idle":"2025-07-18T21:13:41.123463Z","shell.execute_reply.started":"2025-07-18T21:13:23.734127Z","shell.execute_reply":"2025-07-18T21:13:41.119198Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize patches for sanity check","metadata":{}},{"cell_type":"code","source":"print(len(wsi.patch_locations))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T01:16:09.566704Z","iopub.execute_input":"2025-07-16T01:16:09.567503Z","iopub.status.idle":"2025-07-16T01:16:09.572643Z","shell.execute_reply.started":"2025-07-16T01:16:09.567475Z","shell.execute_reply":"2025-07-16T01:16:09.571522Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"patch_tensors = wsi.get_all_patch_tensors()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T01:16:14.685377Z","iopub.execute_input":"2025-07-16T01:16:14.68571Z","iopub.status.idle":"2025-07-16T01:16:43.181355Z","shell.execute_reply.started":"2025-07-16T01:16:14.68568Z","shell.execute_reply":"2025-07-16T01:16:43.180421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(patch_tensors.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T01:16:47.682192Z","iopub.execute_input":"2025-07-16T01:16:47.68251Z","iopub.status.idle":"2025-07-16T01:16:47.687542Z","shell.execute_reply.started":"2025-07-16T01:16:47.682487Z","shell.execute_reply":"2025-07-16T01:16:47.686706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\n\nstart_time = time.perf_counter()\n\npatch_tensor, patch_image = wsi.get_patch(7)\n\nend_time = time.perf_counter()\n\nduration = end_time - start_time\nprint(f\"The code block took {duration:.4f} seconds to execute.\")\nprint(patch_tensor)\n\n# The code block took 154.5610 seconds to execute. openslide 66\n\n# The code block took 105.2772 seconds to execute. pyvips 2666\n# The code block took 140.6164 seconds to execute. openslide 2666","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:13:56.659903Z","iopub.execute_input":"2025-07-18T21:13:56.661454Z","iopub.status.idle":"2025-07-18T21:16:17.290782Z","shell.execute_reply.started":"2025-07-18T21:13:56.661405Z","shell.execute_reply":"2025-07-18T21:16:17.288244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nplt.imshow(patch_image)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T21:09:58.370956Z","iopub.execute_input":"2025-07-18T21:09:58.372284Z","iopub.status.idle":"2025-07-18T21:09:58.699885Z","shell.execute_reply.started":"2025-07-18T21:09:58.372245Z","shell.execute_reply":"2025-07-18T21:09:58.698427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(wsi.patch_locations)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-15T02:23:04.810687Z","iopub.execute_input":"2025-07-15T02:23:04.811426Z","iopub.status.idle":"2025-07-15T02:23:04.815749Z","shell.execute_reply.started":"2025-07-15T02:23:04.811401Z","shell.execute_reply":"2025-07-15T02:23:04.814764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom PIL import Image\n\n# --- 1. Define Thumbnail and Get Image Dimensions ---\n# Get the full resolution image dimensions (width, height)\noriginal_width = wsi.width\noriginal_height = wsi.height\n\npatch_coordinates = wsi.patch_locations\npatch_size = 224\n\n# --- 2. Generate the Thumbnail ---\n# The get_thumbnail function maintains the aspect ratio,\n# creating an image that fits within the given size.\nif wsi.is_tma:\n    thumbnail = wsi.slide.get_thumbnail((1024, 1024))\nelse:\n    thumbnail = Image.open(wsi.thumbnail_path)\n# Get the actual size of the generated thumbnail\nthumb_width, thumb_height = thumbnail.size\n\n# --- 3. Calculate Scaling Factors ---\n# These factors will scale coordinates from the original image to the thumbnail\nwidth_scale = thumb_width / original_width\nheight_scale = thumb_height / original_height\n\n# --- 4. Visualize the Thumbnail and Patches ---\n# Create a figure and axes for plotting\nfig, ax = plt.subplots(figsize=(10, 10))\n\n# Display the thumbnail image\nax.imshow(thumbnail)\n\n# Loop through each patch coordinate to draw it on the thumbnail\nfor x, y in patch_coordinates:\n    # Scale the patch's top-left corner coordinates\n    scaled_x = x * width_scale\n    scaled_y = y * height_scale\n\n    # Scale the patch's dimensions\n    scaled_patch_width = patch_size * width_scale\n    scaled_patch_height = patch_size * height_scale\n\n    # Create a rectangle patch with a red edge and no fill\n    rect = patches.Rectangle(\n        (scaled_x, scaled_y),\n        scaled_patch_width,\n        scaled_patch_height,\n        linewidth=1,\n        edgecolor='r',  # Red color for the patch border\n        facecolor='none'  # No fill\n    )\n\n    # Add the rectangle to the plot\n    ax.add_patch(rect)\n\n# --- 5. Finalize and Show the Plot ---\nax.set_title(\"WSI Thumbnail with Selected Patches\")\nplt.axis('off')  # Hide the axes ticks and labels\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T00:44:51.750025Z","iopub.execute_input":"2025-07-16T00:44:51.750372Z","iopub.status.idle":"2025-07-16T00:44:52.762217Z","shell.execute_reply.started":"2025-07-16T00:44:51.750349Z","shell.execute_reply":"2025-07-16T00:44:52.760478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}