Skip to content

fix: derive tile->mask scale from image dimensions - #19

Merged
vojtech-cifka merged 2 commits into
masterfrom
fix/tissue-coverage-mask-scale
Oct 1, 2026
Merged

vojtech-cifka merged 2 commits into
masterfrom
fix/tissue-coverage-mask-scale

Conversation

@vojtech-cifka

Copy link
Copy Markdown
Collaborator

coverage_stats mapped tile coordinates into mask pixels with tile_mpp / mask_mpp, where mask_mpp was the mpp requested in the config rather than the mask's real resolution. Mask builders resolve their target mpp through OpenSlide.closest_level, which snaps to the nearest existing pyramid level: on TCGA slides with downsamples 1/4/16 a 2.0 mpp request lands at ~1.0, so every tile sampled the tissue mask at half its true position. The kept tile set became the tissue footprint magnified 2x about the origin and clipped at the slide edge, which is why whole-slide prediction maps covered glass in places and missed tissue in others while the predictions themselves were correct.

The tiling level and each mask depict the same physical slide, so the ratio of their pixel dimensions is the scale. Derive it per mask inside the mask loop (masks in one run can differ in resolution) and per axis, which also fixes anisotropic mpp and absorbs the bad_slides mpp*=2 branch in tissue_masks. config.mpp is no longer used for any computation; it stays in the config as provenance and is logged as a run hyperparameter.

coverage_stats mapped tile coordinates into mask pixels with
`tile_mpp / mask_mpp`, where mask_mpp was the mpp requested in the config
rather than the mask's real resolution. Mask builders resolve their target
mpp through OpenSlide.closest_level, which snaps to the nearest existing
pyramid level: on TCGA slides with downsamples 1/4/16 a 2.0 mpp request
lands at ~1.0, so every tile sampled the tissue mask at half its true
position. The kept tile set became the tissue footprint magnified 2x about
the origin and clipped at the slide edge, which is why whole-slide
prediction maps covered glass in places and missed tissue in others while
the predictions themselves were correct.

The tiling level and each mask depict the same physical slide, so the ratio
of their pixel dimensions is the scale. Derive it per mask inside the mask
loop (masks in one run can differ in resolution) and per axis, which also
fixes anisotropic mpp and absorbs the bad_slides mpp*=2 branch in
tissue_masks. config.mpp is no longer used for any computation; it stays in
the config as provenance and is logged as a run hyperparameter.

Verified against the tissue masks on 6 test slides: IoU between the kept
tile footprint and the mask foreground rose from 0.12-0.21 to 0.945-0.976,
and per-slide tile counts moved in both directions (+11% to -10%), as a
de-skew does rather than a uniform rescale.

Existing tissue_stats artifacts are affected; reruns must propagate to the
tissue-tile embeddings, which filter on tile_tissue_coverage and are
inner-joined at predict time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwpvonFg4nhkhPFj1EFb9b
@vojtech-cifka
vojtech-cifka requested a review from vejtek September 3, 2026 16:48
@vojtech-cifka vojtech-cifka self-assigned this Sep 3, 2026
@vojtech-cifka
vojtech-cifka requested review from a team and matejpekar September 3, 2026 16:48
@coderabbitai

coderabbitai Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: b79290e0-a881-499b-acf3-ac7c65919ad1


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

CI runs `uvx ruff check`, which pulls the latest ruff (0.16.5), while the
pinned dev dependency is `ruff>=0.14.5` and resolves to 0.14.5 locally. The
newer version carries ASYNC230, which flags the blocking `open` inside
`qc_main` — invisible to local runs, red on GitHub.

The rule is right on the merits: the append ran on the event loop while QC
results were still streaming in from merge_generators. Move the write into a
sync helper dispatched with asyncio.to_thread. It only runs for failed
slides, so the thread hop costs nothing on the normal path.

Pre-existing on master, unrelated to the coverage_stats fix; surfaced here
because the lint workflow runs on the PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwpvonFg4nhkhPFj1EFb9b
@vojtech-cifka
vojtech-cifka merged commit f0d4c4f into master Oct 1, 2026
3 checks passed
@vojtech-cifka
vojtech-cifka deleted the fix/tissue-coverage-mask-scale branch October 1, 2026 18:52
vojtech-cifka added a commit that referenced this pull request Oct 6, 2026
Both artifacts were regenerated after the tile->mask scale fix (#19).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants