Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2a9a59b
feat: add tiling run_id
vojtech-cifka Apr 22, 2026
bb6dc1b
feat: add configs for tile masks
vojtech-cifka Apr 22, 2026
8b5e5ac
feat: implement tile mask pipeline
vojtech-cifka Apr 22, 2026
04283b2
feat: add sumbission script for the tile masks
vojtech-cifka Apr 22, 2026
654710a
fix: group by slides before the loop
vojtech-cifka Apr 22, 2026
93eecb9
fix: update submission script to use GitHub repository URL
vojtech-cifka Apr 22, 2026
682a5d3
perf: load only required columns from tiles parquet
vojtech-cifka Apr 22, 2026
cb29d16
perf: replace PIL tile_mask with vectorized numpy implementation
vojtech-cifka Apr 22, 2026
7d2e475
fix: correct shape mismatch in vectorized tile outline drawing
vojtech-cifka Apr 22, 2026
b94b3f8
fix: clip out-of-bounds tile pixels in vectorized outline drawing
vojtech-cifka Apr 22, 2026
ef4e51e
perf: add configurable downsampling to tile mask generation
vojtech-cifka Apr 22, 2026
f96c636
style: drop comments with ambiguous unicode multiplication sign
vojtech-cifka Apr 22, 2026
8504b84
chore: increase tile mask downsample to 8x to avoid OOM
vojtech-cifka Apr 22, 2026
6ad1b62
fix: tile boundaries
vojtech-cifka Apr 24, 2026
d2d1e7c
fix: set default downsample to 8 in base config, remove redundant ove…
vojtech-cifka Apr 24, 2026
fc51a29
fix: use slide id as output filename to prevent cross-source collisions
vojtech-cifka Apr 24, 2026
bf6ae43
fix: mypy
vojtech-cifka Apr 25, 2026
b951514
Merge remote-tracking branch 'origin/master' into feature/implement-t…
vojtech-cifka Apr 27, 2026
b32c6b6
fix(tile_masks): resolve mypy type errors
vojtech-cifka Apr 27, 2026
de381f3
refactor(tile_masks): use rationai.masks.tile_mask and name outputs b…
vojtech-cifka Apr 28, 2026
1f6f1d9
fix(tile_masks): use 'path' column for WSI filename
vojtech-cifka Apr 28, 2026
979d4ea
fix(tile_masks): ceil canvas size to avoid clipping edge tiles
vojtech-cifka Apr 28, 2026
97f3951
fix: use stride as the box size
vojtech-cifka Apr 29, 2026
a302ed7
fix: tile masks allignment
vojtech-cifka Apr 29, 2026
a0f1011
feat: use ScalarMaskBuilder
vojtech-cifka Apr 29, 2026
1884819
feat: compute also tile coverage with heatmap
vojtech-cifka Apr 29, 2026
760e13a
feat(tile_masks): add per-class coverage heatmaps and configure submi…
vojtech-cifka Apr 29, 2026
9cff04d
fix: remove heatmap generation
vojtech-cifka Apr 29, 2026
c35e29d
fix: downsample the images
vojtech-cifka Apr 29, 2026
3984431
fix: remove directory
vojtech-cifka Apr 29, 2026
98f435b
fix: remove downsample and free memory bounds
vojtech-cifka Apr 29, 2026
64501fc
feat: use numpy
vojtech-cifka Apr 29, 2026
67f0b2d
fix: lower concurrency
vojtech-cifka Apr 29, 2026
fa9c8ec
feat: bring back ray
vojtech-cifka Apr 29, 2026
4e24325
refactor: drop ray, add heatmaps
vojtech-cifka Apr 29, 2026
432ad82
fix: bring back ray but dump the memory
vojtech-cifka Apr 29, 2026
72f12a9
fix: generate the mask in place
vojtech-cifka Apr 30, 2026
096119a
fix: raise concurrency
vojtech-cifka Apr 30, 2026
79c343d
fix: lower concurrency to 4
vojtech-cifka Apr 30, 2026
1eecb84
feat: lower concurrency to 1
vojtech-cifka Apr 30, 2026
8d02a50
refactor: drop ray
vojtech-cifka May 3, 2026
524d116
refactor: drop Ray from tile_masks, simplify slide iteration
vojtech-cifka May 3, 2026
b9c6aab
fix: lint
vojtech-cifka May 3, 2026
c659d15
Merge branch 'master' into feature/implement-tile-masks
vojtech-cifka May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions configs/experiment/preprocessing/tile_masks_05mpp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# @package _global_

defaults:
- /data: dataset
- _self_

slides_artifact_path: train_split/slides.parquet
tiles_artifact_path: train_split/tiles.parquet
tile_percentage_cols:
- tile_coverage_Nerve
- tile_coverage_Blood
- tile_coverage_Connective-Tissue
- tile_coverage_Fat
- tile_coverage_Epithelium
- tile_coverage_Muscle
- tile_coverage_Other
12 changes: 12 additions & 0 deletions configs/preprocessing/tile_masks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @package _global_

slides_artifact_path: ???
tiles_artifact_path: ???
tile_percentage_cols: ???

max_concurrent: 1
mlflow_artifact_path: tile_masks

metadata:
run_name: Tile Masks ${dataset.name}
description: Tile mask generation
107 changes: 107 additions & 0 deletions preprocessing/tile_masks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any, cast

import hydra
import mlflow
import pandas as pd
import pyvips
import torch
from omegaconf import DictConfig
from rationai.masks import tile_mask, write_big_tiff
from rationai.masks.mask_builders import ScalarMaskBuilder
from rationai.mlkit import autolog, with_cli_args
from rationai.mlkit.lightning.loggers import MLFlowLogger
from tqdm import tqdm


def process_slide(
slide: dict[str, Any],
output_dir: str,
tile_percentage_cols: list[str],
tiles_path: str,
) -> None:
slide_tiles = pd.read_parquet(
tiles_path,
columns=["x", "y", *tile_percentage_cols],
filters=[("slide_id", "==", slide["id"])],
)
filename = f"{Path(slide['path']).stem}.tiff"

for percentage_col in tile_percentage_cols:
builder = ScalarMaskBuilder(
save_dir=Path(output_dir, percentage_col),
filename=filename,
extent_x=slide["extent_x"],
extent_y=slide["extent_y"],
mpp_x=slide["mpp_x"],
mpp_y=slide["mpp_y"],
extent_tile=slide["tile_extent_x"],
stride=slide["stride_x"],
)
builder.update(
data=torch.tensor(slide_tiles[percentage_col].values).unsqueeze(1),
xs=torch.tensor(slide_tiles["x"].values),
ys=torch.tensor(slide_tiles["y"].values),
)
builder.save()

mask = tile_mask(
slide_tiles,
tile_extent=(slide["tile_extent_x"], slide["tile_extent_y"]),
size=(slide["extent_x"], slide["extent_y"]),
)
width, height = mask.size
mask_bytes = mask.tobytes()
del mask

write_big_tiff(
image=pyvips.Image.new_from_memory(
data=mask_bytes,
width=width,
height=height,
bands=1,
format="uchar",
),
path=Path(output_dir, "outlines", filename),
mpp_x=slide["mpp_x"],
mpp_y=slide["mpp_y"],
)


@with_cli_args(["+preprocessing=tile_masks"])
@hydra.main(config_path="../configs", config_name="preprocessing", version_base=None)
@autolog
def main(config: DictConfig, logger: MLFlowLogger) -> None:
tiling_run_id = config.dataset.mlflow_artifacts.tiling_run_id
tile_percentage_cols: list[str] = list(config.tile_percentage_cols)

slides_path = mlflow.artifacts.download_artifacts(
run_id=tiling_run_id,
artifact_path=config.slides_artifact_path,
)
tiles_path = mlflow.artifacts.download_artifacts(
run_id=tiling_run_id,
artifact_path=config.tiles_artifact_path,
)
slides = pd.read_parquet(slides_path)

items = cast("list[dict[str, Any]]", slides.to_dict(orient="records"))

with TemporaryDirectory() as output_dir:
Comment thread
vojtech-cifka marked this conversation as resolved.
Path(output_dir, "outlines").mkdir()
for slide in tqdm(items):
process_slide(
slide,
output_dir=output_dir,
tile_percentage_cols=tile_percentage_cols,
tiles_path=tiles_path,
)

logger.log_artifacts(
local_dir=output_dir, artifact_path=config.mlflow_artifact_path
)
Comment thread
vojtech-cifka marked this conversation as resolved.


if __name__ == "__main__":
main()
18 changes: 18 additions & 0 deletions scripts/submit_tile_masks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from kube_jobs import storage, submit_job


submit_job(
job_name="tissue-classification-tile-masks",
username=...,
cpu=8,
memory="64Gi",
gpu=None,
public=False,
script=[
"git clone https://github.com/RationAI/tissue-classification.git workdir",
"cd workdir",
"uv sync",
"uv run python -m preprocessing.tile_masks +experiment=...",
],
storage=[storage.secure.DATA],
)
Loading