From c9ed363e27c697f2ce75991f439b63476662f3cd Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Tue, 30 Dec 2025 12:23:34 +0000 Subject: [PATCH 1/7] sync hs2p --- slide2vec/hs2p | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slide2vec/hs2p b/slide2vec/hs2p index f1a9def..faef5a9 160000 --- a/slide2vec/hs2p +++ b/slide2vec/hs2p @@ -1 +1 @@ -Subproject commit f1a9defa3528c129b933b9b7b3fbea0dddd9056e +Subproject commit faef5a9a621c68d20e13550e30bfd27ae1983ebe From 83df8c16e414d3b5a336c00bf50fdc1ac7af2138 Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Wed, 31 Dec 2025 13:58:30 +0000 Subject: [PATCH 2/7] add option to restrict tile to tissue content during embedding step --- slide2vec/configs/default.yaml | 5 ++- slide2vec/data/dataset.py | 72 +++++++++++++++++++++++++++++++--- slide2vec/embed.py | 63 +++++++++++++++++++++++++---- slide2vec/hs2p | 2 +- 4 files changed, 126 insertions(+), 16 deletions(-) diff --git a/slide2vec/configs/default.yaml b/slide2vec/configs/default.yaml index 0b772d2..10b510f 100644 --- a/slide2vec/configs/default.yaml +++ b/slide2vec/configs/default.yaml @@ -20,7 +20,7 @@ tiling: drop_holes: false # whether or not to drop tiles whose center pixel falls withing an identified holes use_padding: true # whether to pad the border of the slide seg_params: - downsample: 64 # find the closest downsample in the slide for tissue segmentation + downsample: 32 # find the closest downsample in the slide for tissue segmentation sthresh: 8 # segmentation threshold (positive integer, using a higher threshold leads to less foreground and more background detection) (not used when use_otsu=True) sthresh_up: 255 # upper threshold value for scaling the binary mask mthresh: 7 # median filter size (positive, odd integer) @@ -28,7 +28,7 @@ tiling: use_otsu: false # use otsu's method instead of simple binary thresholding tissue_pixel_value: 1 # value of tissue pixel in pre-computed segmentation masks filter_params: - ref_tile_size: 16 # reference tile size at spacing tiling.spacing + ref_tile_size: ${tiling.params.tile_size} # reference tile size at spacing tiling.spacing a_t: 4 # area filter threshold for tissue (positive integer, the minimum size of detected foreground contours to consider, relative to the reference tile size ref_tile_size, e.g. a value 10 means only detected foreground contours of size greater than 10 [ref_tile_size, ref_tile_size] tiles at spacing tiling.spacing will be kept) a_h: 2 # area filter threshold for holes (positive integer, the minimum size of detected holes/cavities in foreground contours to avoid, once again relative to the reference tile size ref_tile_size) max_n_holes: 8 # maximum of holes to consider per detected foreground contours (positive integer, higher values lead to more accurate patching but increase computational cost ; keeps the biggest holes) @@ -43,6 +43,7 @@ model: pretrained_weights: # path to the pretrained weights when using a custom model batch_size: 256 tile_size: ${tiling.params.tile_size} + restrict_to_tissue: false # whether to restrict tile content to tissue pixels only when feeding tile through encoder patch_size: 256 # if level is "region", size used to unroll the region into patches save_tile_embeddings: false # whether to save tile embeddings alongside the pooled slide embedding when level is "slide" save_latents: false # whether to save the latent representations from the model alongside the slide embedding (only supported for 'prism') diff --git a/slide2vec/data/dataset.py b/slide2vec/data/dataset.py index 3a2f034..55ca74d 100644 --- a/slide2vec/data/dataset.py +++ b/slide2vec/data/dataset.py @@ -1,3 +1,4 @@ +import cv2 import torch import numpy as np import wholeslidedata as wsd @@ -5,27 +6,67 @@ from transformers.image_processing_utils import BaseImageProcessor from PIL import Image from pathlib import Path +from typing import Callable + +from slide2vec.hs2p.hs2p.wsi import WholeSlideImage, SegmentationParameters, SamplingParameters, FilterParameters +from slide2vec.hs2p.hs2p.wsi.utils import HasEnoughTissue class TileDataset(torch.utils.data.Dataset): - def __init__(self, wsi_path, tile_dir, target_spacing, backend, transforms=None): + def __init__( + self, + wsi_path: Path, + mask_path: Path, + coordinates_dir: Path, + target_spacing: float, + tolerance: float, + backend: str, + segment_params: SegmentationParameters | None = None, + sampling_params: SamplingParameters | None = None, + filter_params: FilterParameters | None = None, + transforms: BaseImageProcessor | Callable | None = None, + restrict_to_tissue: bool = False, + ): self.path = wsi_path + self.mask_path = mask_path self.target_spacing = target_spacing self.backend = backend self.name = wsi_path.stem.replace(" ", "_") - self.load_coordinates(tile_dir) + self.load_coordinates(coordinates_dir) self.transforms = transforms + self.restrict_to_tissue = restrict_to_tissue + + if restrict_to_tissue: + _wsi = WholeSlideImage( + path=self.path, + mask_path=self.mask_path, + backend=self.backend, + segment_params=segment_params, + sampling_params=sampling_params, + ) + contours, holes = _wsi.detect_contours( + target_spacing=target_spacing, + tolerance=tolerance, + filter_params=filter_params, + ) + scale = _wsi.level_downsamples[_wsi.seg_level] + self.contours = _wsi.scaleContourDim(contours, (1.0 / scale[0], 1.0 / scale[1])) + self.holes = _wsi.scaleHolesDim(holes, (1.0 / scale[0], 1.0 / scale[1])) + self.tissue_mask = _wsi.annotation_mask["tissue"] + self.seg_spacing = _wsi.get_level_spacing(_wsi.seg_level) + self.spacing_at_level_0 = _wsi.get_level_spacing(0) - def load_coordinates(self, tile_dir): - coordinates = np.load(Path(tile_dir, f"{self.name}.npy"), allow_pickle=True) + def load_coordinates(self, coordinates_dir): + coordinates = np.load(Path(coordinates_dir, f"{self.name}.npy"), allow_pickle=True) self.x = coordinates["x"] self.y = coordinates["y"] + self.contour_index = coordinates["contour_index"] self.coordinates = (np.array([self.x, self.y]).T).astype(int) self.scaled_coordinates = self.scale_coordinates() self.tile_level = coordinates["tile_level"] self.tile_size_resized = coordinates["tile_size_resized"] - resize_factor = coordinates["resize_factor"] - self.tile_size = np.round(self.tile_size_resized / resize_factor).astype(int) + self.resize_factor = coordinates["resize_factor"] + self.tile_size = np.round(self.tile_size_resized / self.resize_factor).astype(int) self.tile_size_lv0 = coordinates["tile_size_lv0"][0] def scale_coordinates(self): @@ -55,6 +96,25 @@ def __getitem__(self, idx): spacing=tile_spacing, center=False, ) + if self.restrict_to_tissue: + contour_idx = self.contour_index[idx] + contour = self.contours[contour_idx] + holes = self.holes[contour_idx] + tissue_checker = HasEnoughTissue( + contour=contour, + contour_holes=holes, + tissue_mask=self.tissue_mask, + tile_size=self.tile_size[idx], + tile_spacing=tile_spacing, + resize_factor=self.resize_factor[idx], + seg_spacing=self.seg_spacing, + spacing_at_level_0=self.spacing_at_level_0, + ) + tissue_mask = tissue_checker.get_tile_mask(self.x[idx], self.y[idx]) + # ensure mask is the same size as the tile + assert tissue_mask.shape[:2] == tile_arr.shape[:2], "Mask and tile shapes do not match" + # apply mask + tile_arr = cv2.bitwise_and(tile_arr, tile_arr, mask=tissue_mask) tile = Image.fromarray(tile_arr).convert("RGB") if self.tile_size[idx] != self.tile_size_resized[idx]: tile = tile.resize((self.tile_size[idx], self.tile_size[idx])) diff --git a/slide2vec/embed.py b/slide2vec/embed.py index 06fb022..4c5d951 100644 --- a/slide2vec/embed.py +++ b/slide2vec/embed.py @@ -18,6 +18,7 @@ from slide2vec.utils.config import get_cfg_from_file, setup_distributed from slide2vec.models import ModelFactory from slide2vec.data import TileDataset, RegionUnfolding +from slide2vec.hs2p.hs2p.wsi import SamplingParameters torchvision.disable_beta_transforms_warning() @@ -60,13 +61,31 @@ def create_transforms(cfg, model): raise ValueError(f"Unknown model level: {cfg.model.level}") -def create_dataset(wsi_fp, coordinates_dir, spacing, backend, transforms): +def create_dataset( + wsi_path, + mask_path, + coordinates_dir, + target_spacing, + tolerance, + backend, + segment_params, + sampling_params, + filter_params, + transforms, + restrict_to_tissue: bool, +): return TileDataset( - wsi_fp, - coordinates_dir, - spacing, + wsi_path=wsi_path, + mask_path=mask_path, + coordinates_dir=coordinates_dir, + target_spacing=target_spacing, + tolerance=tolerance, backend=backend, + segment_params=segment_params, + sampling_params=sampling_params, + filter_params=filter_params, transforms=transforms, + restrict_to_tissue=restrict_to_tissue, ) @@ -176,12 +195,30 @@ def main(args): if not run_on_cpu: torch.distributed.barrier() + pixel_mapping = {k: v for e in cfg.tiling.sampling_params.pixel_mapping for k, v in e.items()} + tissue_percentage = {k: v for e in cfg.tiling.sampling_params.tissue_percentage for k, v in e.items()} + if "tissue" not in tissue_percentage: + tissue_percentage["tissue"] = cfg.tiling.params.min_tissue_percentage + if cfg.tiling.sampling_params.color_mapping is not None: + color_mapping = {k: v for e in cfg.tiling.sampling_params.color_mapping for k, v in e.items()} + else: + color_mapping = None + + sampling_params = SamplingParameters( + pixel_mapping=pixel_mapping, + color_mapping=color_mapping, + tissue_percentage=tissue_percentage, + ) + # select slides that were successfully tiled but not yet processed for feature extraction tiled_df = process_df[process_df.tiling_status == "success"] mask = tiled_df["feature_status"] != "success" process_stack = tiled_df[mask] total = len(process_stack) + wsi_paths_to_process = [Path(x) for x in process_stack.wsi_path.values.tolist()] + mask_paths_to_process = [Path(x) for x in process_stack.mask_path.values.tolist()] + combined_paths = zip(wsi_paths_to_process, mask_paths_to_process) features_dir = Path(cfg.output_dir, "features") if distributed.is_main_process(): @@ -201,8 +238,8 @@ def main(args): transforms = create_transforms(cfg, model) print(f"transforms: {transforms}") - for wsi_fp in tqdm.tqdm( - wsi_paths_to_process, + for wsi_fp, mask_fp in tqdm.tqdm( + combined_paths, desc="Inference", unit="slide", total=total, @@ -211,7 +248,19 @@ def main(args): position=1, ): try: - dataset = create_dataset(wsi_fp, coordinates_dir, cfg.tiling.params.spacing, cfg.tiling.backend, transforms) + dataset = create_dataset( + wsi_path=wsi_fp, + mask_path=mask_fp, + coordinates_dir=coordinates_dir, + target_spacing=cfg.tiling.params.spacing, + tolerance=cfg.tiling.params.tolerance, + backend=cfg.tiling.backend, + segment_params=cfg.tiling.seg_params, + sampling_params=sampling_params, + filter_params=cfg.tiling.filter_params, + transforms=transforms, + restrict_to_tissue=cfg.model.restrict_to_tissue, + ) if distributed.is_enabled_and_multiple_gpus(): sampler = torch.utils.data.DistributedSampler( dataset, diff --git a/slide2vec/hs2p b/slide2vec/hs2p index faef5a9..94ae12c 160000 --- a/slide2vec/hs2p +++ b/slide2vec/hs2p @@ -1 +1 @@ -Subproject commit faef5a9a621c68d20e13550e30bfd27ae1983ebe +Subproject commit 94ae12cede5bfcc72ff3573015d5de9792b9372a From d8efd988657c5b086d5c20a77046f54d90164dae Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Wed, 31 Dec 2025 15:00:00 +0000 Subject: [PATCH 3/7] sync hs2p --- slide2vec/data/dataset.py | 14 +++++++------- slide2vec/hs2p | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/slide2vec/data/dataset.py b/slide2vec/data/dataset.py index 55ca74d..6e2b741 100644 --- a/slide2vec/data/dataset.py +++ b/slide2vec/data/dataset.py @@ -60,13 +60,13 @@ def load_coordinates(self, coordinates_dir): coordinates = np.load(Path(coordinates_dir, f"{self.name}.npy"), allow_pickle=True) self.x = coordinates["x"] self.y = coordinates["y"] - self.contour_index = coordinates["contour_index"] self.coordinates = (np.array([self.x, self.y]).T).astype(int) self.scaled_coordinates = self.scale_coordinates() + self.contour_index = coordinates["contour_index"] + self.target_tile_size = coordinates["target_tile_size"] self.tile_level = coordinates["tile_level"] - self.tile_size_resized = coordinates["tile_size_resized"] self.resize_factor = coordinates["resize_factor"] - self.tile_size = np.round(self.tile_size_resized / self.resize_factor).astype(int) + self.tile_size_resized = coordinates["tile_size_resized"] self.tile_size_lv0 = coordinates["tile_size_lv0"][0] def scale_coordinates(self): @@ -104,7 +104,7 @@ def __getitem__(self, idx): contour=contour, contour_holes=holes, tissue_mask=self.tissue_mask, - tile_size=self.tile_size[idx], + tile_size=self.target_tile_size[idx], tile_spacing=tile_spacing, resize_factor=self.resize_factor[idx], seg_spacing=self.seg_spacing, @@ -116,10 +116,10 @@ def __getitem__(self, idx): # apply mask tile_arr = cv2.bitwise_and(tile_arr, tile_arr, mask=tissue_mask) tile = Image.fromarray(tile_arr).convert("RGB") - if self.tile_size[idx] != self.tile_size_resized[idx]: - tile = tile.resize((self.tile_size[idx], self.tile_size[idx])) + if self.target_tile_size[idx] != self.tile_size_resized[idx]: + tile = tile.resize((self.target_tile_size[idx], self.target_tile_size[idx])) if self.transforms: - if isinstance(self.transforms, BaseImageProcessor): # Hugging Face (`transformer`) + if isinstance(self.transforms, BaseImageProcessor): # Hugging Face (`transformer`) tile = self.transforms(tile, return_tensors="pt")["pixel_values"].squeeze(0) else: # general callable such as torchvision transforms tile = self.transforms(tile) diff --git a/slide2vec/hs2p b/slide2vec/hs2p index 94ae12c..e63953e 160000 --- a/slide2vec/hs2p +++ b/slide2vec/hs2p @@ -1 +1 @@ -Subproject commit 94ae12cede5bfcc72ff3573015d5de9792b9372a +Subproject commit e63953eb040190cafb3dd36fe2348724af92a24b From 60f743d1c5a5e1e98823830d86f0f5b3bc2ff882 Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Wed, 31 Dec 2025 15:10:42 +0000 Subject: [PATCH 4/7] update gt coordinates --- test/gt/test-wsi.npy | Bin 25960 -> 33368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/gt/test-wsi.npy b/test/gt/test-wsi.npy index c68ca54450369cf4f1b27505ec8cfcf721e43161..d3eadd4c142ba4edbb596da30f83bc6372508528 100644 GIT binary patch literal 33368 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 literal 25960 zcmbWko=QF-q|VMhPZj)gl$D2s(&#Q5+O1Dj~GDrV!eagi^Kcf*k}G zhYpSo9XdJ`@egotaCGP(xHve7i-OkP^HPFbz3-EuhopHW=brELe4cYp?meHr`o=4- zy}4)M*M$#Oj!(9aZLK`F^w#Rixs?M;E6<-iyz89^ieaov0 z*FU~poNlk|nQs02(#M-W{`S=$FYNyM)fc!6_l|Q%_K$OW?;Gd7zJHuM|6p@hADe!E zJuX~5E?hk>TsTZ-A}IW=eGx^ z`?+}cbh|r878YjZ3s-ZAtNq8-<8tBY>Eo*V$<_Vj>V6(MG_GEDZk)OwR@(Y;pAUIB z@49nu4|!4D{-J-X`|inckMEPAzU%M1dfT|)ckzyK?&w|PTydCgsldCk?l=4xJZHLtmv z*PMB2JDoDKpWkvd=ee5mT+Ml|<~&z(o~t>})tu*Q&T}>AxtjCbxf|N~3s>g@SLXv) z=ffAn`JBF)w~x3wSGYP?xH?z3I#;+lSGYPKxH=!WOL0DM8}WRetMh@Y^MR}Lfm6@6 z>U`GqPp-}fuFeOp&Ihi}2d>TsuFeOp&Ihi}2d>TsuFeOp&Ihi}2d>TsuFi+!!}E51 zGjAVpbv|%)K5%tDaCJU#bv|%)K5%tDaCJU#8*x5x%kg>-SLXv)=L4snZPod#`M}lr zz}5M{)%n2H`M}lrz}5M{)%n2H`M}lrz}5M{)%n2H`M}lrz}5M%bo;bpYR+>tce$Fo zT+Lms<}O!rm#ewUEyvvDYVLA1cR6`$`+vF1)!gN3?s7GExthCN&0VhME?0AxtGUb7 z+~sQSay56kn!8-hU9RTt_rvQE^v%3|#MQj!YJPGxKe?KpT+L6e<|kM4le-vSSHf9u zTXit&yp^l@$<_SiYJPGxKe?KpT+L6e<|kM4ldJj3)%@gYesVQGxtgC`&Ckz<*JbIO zdHaZ~Imp!<%;ZUynV#g9OP;aay19JnuA=;L9XT?S96fF zZnoBQRt|DUV-9kQ@qGnc%|Wi_AXjsct2xNk9OP;aay19JnuA=;L9XT?S99?9;eB-V z`7Kxf?!?uSI-N$dS1ZQ^8&7(7jX5wfUD;PoOQFcs!1RjH`LZ z)jZ>Bo^dtLxSD5N%`>i^7jQKPzZu^5u8!xeYp&)JS96K0xy036;%Y8&HJ7-WOPqDH zwVtzbiL1H9)m-9gE^!y){TaBLOI*z*uI3U~bBU|D#MNBlYA$g#mp&Tqt5NfXtNFs! zeBo-oa5Z1JnlD_<7f!C&{$IXuHD9=zFI>$R?n2BL?nu1<3s>`ntNFs!eBo-oa5Z1J znlCR7g;=joaP|5GSFcZSb^W-yeq3EYuC5cU$~ktT+J7*<_lNn!@>g#*FS2% zbG6^O+V5QLcdqt3SNol-{m#{X=W4%mwcokg?_BM7Zg1>&uJ$`u`<<)(er34tVC^HW z_7PY6h^u|X)jr~CA91yhxY|ct?IW)C5m)<&t9``PKH|>D`)YEvkIoJE&8q#t)qdb= zKXA1lxY`d~?FX**16TWjtNp;$e&A|9aJ3(}+7E{wo}Qo8y5?$KbG5FyTGw2yYp&Kc zSL>Rqb(146=jyt1b=|qT?p$4WuC6;**PW~D{^fB0qzb=|&DFZ*YF%?<>-uoKPq5!tuPbtw<9&j;&3K<+uKwL(W4Nzy Yt!u8 Date: Wed, 31 Dec 2025 15:19:46 +0000 Subject: [PATCH 5/7] dummy gitignore edit to force PR test --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b24c1b5..f815d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,5 @@ cython_debug/ # custom output/ +outputs/ archive/ From 4cf83e0736a58819a9ef86be792d0e9a3e6fef5a Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Wed, 31 Dec 2025 15:48:59 +0000 Subject: [PATCH 6/7] fix submodule import error --- slide2vec/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/slide2vec/__init__.py b/slide2vec/__init__.py index 8c0d5d5..1e97492 100644 --- a/slide2vec/__init__.py +++ b/slide2vec/__init__.py @@ -1 +1,6 @@ __version__ = "2.0.0" + +import sys +import os + +sys.path.append(os.path.join(os.path.dirname(__file__), "hs2p")) From 4f162e2c5265eade416127dd7f3bff50157da254 Mon Sep 17 00:00:00 2001 From: clemsgrs Date: Thu, 1 Jan 2026 13:38:48 +0000 Subject: [PATCH 7/7] restore legacy downsample to pass test --- slide2vec/configs/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slide2vec/configs/default.yaml b/slide2vec/configs/default.yaml index 10b510f..82fab7e 100644 --- a/slide2vec/configs/default.yaml +++ b/slide2vec/configs/default.yaml @@ -20,7 +20,7 @@ tiling: drop_holes: false # whether or not to drop tiles whose center pixel falls withing an identified holes use_padding: true # whether to pad the border of the slide seg_params: - downsample: 32 # find the closest downsample in the slide for tissue segmentation + downsample: 64 # find the closest downsample in the slide for tissue segmentation sthresh: 8 # segmentation threshold (positive integer, using a higher threshold leads to less foreground and more background detection) (not used when use_otsu=True) sthresh_up: 255 # upper threshold value for scaling the binary mask mthresh: 7 # median filter size (positive, odd integer)