Skip to content

fix: open_wsi keeps slides out of each other's stores - #88

Merged
Mr-Milk merged 4 commits into
mainfrom
fix/open-wsi-store-folder
Oct 1, 2026
Merged

Mr-Milk merged 4 commits into
mainfrom
fix/open-wsi-store-folder

Conversation

@Mr-Milk

@Mr-Milk Mr-Milk commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Summary

New store folders. open_wsi(slide, store="data") took data as a folder of stores only when it already existed. On a fresh disk it made data the store of the first slide. Every later slide opened with store="data" then found data to be a zarr store and used it, and its write() went into the first slide's store. In the lazyslide-tutorials multiple_slides notebook, 45 GTEx slides went into one store named data, and agg_wsi then raised "File data/.zarr not existed." The notebook worked where data/ already existed.

A new store path is now the store only with a .zarr suffix. Any other new path is a directory, as an existing one already was: store="data" gives data/<stem>.zarr (or data/<stem>.scene-<scene>.zarr), and write() creates data/. Unchanged: .zarr paths, new or existing; existing zarr stores, whatever their name; existing directories; existing files.

The store of another slide raises. _validate_store_scene only checks the scene. So the store of another slide was loaded, WSIData replaced its slide properties with the opened slide's, and the next write() merged the two slides. open_wsi now compares the level-0 shape in the store's slide_properties with the reader's, and raises a ValueError naming both when they differ:

Store 'data' belongs to a slide of shape [2967, 2220], but .../GTEX-1117F-0526.svs has shape [19958, 19919]. Use the store of this slide, or a directory that holds a store for each slide.

Every store written since the slide properties moved to attrs holds the shape, so this also catches the data stores the bug has already made. Stores without a shape are not checked.

Behaviour changes:

  • A new store path without .zarr that was meant as the store itself, such as store="out/a", is now a folder: the store goes to out/a/<stem>.zarr. LazySlide's source and the tutorials pass .zarr paths (sample.zarr, temp.zarr) or a folder ("data").
  • The store of the same slide written by a reader that reads another level-0 size raises too. Its tissue and tile coordinates would be in another frame.

Options weighed

  • (a) Check that a reused store belongs to the slide: done. It compares the shape the stores already hold instead of adding a file name or size attr, so it needs no new attr and covers the stores written before. It also tells apart same-named slides from different folders, the collision a store folder still allows, which a file name would not.
  • (b) A path without .zarr is a folder: done, only for paths that do not exist yet.
  • (c) Warn: not needed with (a) and (b).

Tests

Each failed before the fix:

  • test_store_folder_keeps_the_stores_of_slides_apart: two slides opened with store=str(tmp_path / "data"), each written. data/ holds GTEX-1117F-0526.zarr and sample.zarr; before, it was one store (['zarr.json']).
  • test_store_of_another_slide_raises: data written as the store of sample.svs, then open_wsi(GTEX-1117F-0526.svs, store="data") raises; before, it did not.

The repro from the report, with lazyslide find_tissues and write(), now gives data/GTEX-O5YU-0926.zarr and data/GTEX-111YS-2226.zarr.

Full suite, merged with main: 129 passed, 14 skipped on Python 3.14; 138 passed, 5 skipped on 3.13 with pylibCZIrw (multi-scene stores). ruff passes.

Once released, the store=f"data/{slide}.zarr" workaround in lazyslide-tutorials (ci/hf-heavy-render) is no longer needed, though it keeps working.

Not fixed

Two slides of one level-0 shape given one store, such as same-named slides from different folders opened with one store folder, still share it without an error. A slide id in the store would catch that.

open_wsi(slide, store="data") made data the store of the first slide
when data did not exist yet, so every later slide opened with
store="data" loaded that store, and its write() went into it.

A new store path without a .zarr suffix is now a directory that holds
a store for each slide, data/<stem>.zarr, as an existing directory
already was. A .zarr path, an existing store and an existing file are
the store, as before.

A store whose slide has another level-0 shape than the opened slide
now raises a ValueError instead of being loaded and overwritten. The
stores written before hold the shape too, so the data stores the bug
has made are caught.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 08:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation matches the documented behavior and includes focused regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes open_wsi store collisions between slides.

Changes:

  • Treats new non-.zarr paths as store directories.
  • Validates reused stores against the slide’s level-0 shape.
  • Adds regression tests for isolation and mismatched stores.
File Description
wsidata/​io/​_wsi.py Resolves safe store paths and validates slide shape.
tests/​test_wsidata.py Covers per-slide stores and mismatch rejection.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Mr-Milk
Mr-Milk enabled auto-merge (squash) October 1, 2026 08:58
@Mr-Milk
Mr-Milk merged commit fae9682 into main Oct 1, 2026
15 checks passed
@Mr-Milk
Mr-Milk deleted the fix/open-wsi-store-folder branch October 1, 2026 09:01
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.

2 participants