Skip to content

Fix four behaviour changes introduced by the mypy adoption in #411 - #418

Merged
LucaMarconato merged 14 commits into
mainfrom
fix/pr-411-regressions
Sep 9, 2026
Merged

Fix four behaviour changes introduced by the mypy adoption in #411#418
LucaMarconato merged 14 commits into
mainfrom
fix/pr-411-regressions

Conversation

@LucaMarconato

@LucaMarconato LucaMarconato commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes #417. All four problems were introduced by the mypy adoption in #411, where a type error was silenced by restructuring code rather than by annotating it.

AI-generated.

Changes

Commit Reader Fix
b3e50bf merscope importlib.import_module("rioxarray") instead of find_spec, so a rioxarray that is installed but cannot be imported (broken rasterio) falls back to the dask_image backend again. Catches ImportError, not only ModuleNotFoundError
6b908f5 dbit restore the original path handling so dbit() no longer defaults to Path(".") and reads the cwd. _check_path's path is typed Path | None and raises when a directory search is needed but no directory was given, which is what the removed # type: ignore was standing in for. dbit(anndata_path=..., barcode_position=...) keeps working
aba347d macsima widen the two guards that exist to tolerate unparsable files (create_sdata's physical-size loop, narrowed in #411, and MultiChannelImage.from_paths, whose except ValueError predates it) — a truncated TIFF raises struct.error
49c5d20, 1243e53 visium test coverage, see below
f921772 changelog
bd5ddfc mypy no longer follows tests/data into the downloaded datasets

Problem 1 of #417, the visium() TypeError, is already fixed on main by #416; this branch is rebased on top of it and adds the tests that were missing.

The two except Exception guards carry # noqa: BLE001 and a comment: they apply a third-party parser to arbitrary files the user left in a folder, so the failure modes are not enumerable — struct.error is one, and tifffile.TiffFileError has changed base class between releases.

Dropped since the first version of this PR

aba347d originally also rounded the OME plane positions instead of truncating them with int(). That is reverted in 7ff0ebe: the positions are physical coordinates (Plane.position_x, carrying a position_x_unit) used directly as da.pad widths in pixels, so the unit may be the real problem, and rounding only removes a sub-pixel bias. Deciding that needs someone with real MACSima data, so it is #419 now, and this branch leaves the behaviour as it is on main.

No changelog entry

f921772 removes the ### Fixed block rather than extending it. v0.7.1 was tagged on 2026-05-15, #411 landed on 2026-08-11, and no tag contains it: none of these bugs ever reached a release, so describing them as fixes would announce breakage no user could have hit. The net diff against main is the removal of the entry #416 added; the regressions are churn inside the template migration that the existing ### Changed entry already covers.

Tests

visium had no test at all, which is how a hard TypeError on the first call shipped. This adds the 10x Space Ranger 2.1.0 CytAssist FFPE Human Breast Cancer dataset (CC BY 4.0) to prepare_test_data.yaml — only the filtered matrix and the spatial/ archive, ~65 MB; the full-resolution image is ~2 GB and fullres_image_file is optional — and builds a reader test suite on it, in the style of the xenium, visium_hd and macsima ones: element names and coordinate systems, the extent of each of the three coordinate systems, the two downscaled images, the table and its annotation, the circles, the dataset_id inferred from the counts file, and a CLI roundtrip.

The property that broke is test_visium_circles_are_the_spot_coordinates: the circles are the spot coordinates of the table, in the order of the table, with the radius from the scalefactors, compared against spatial/tissue_positions.csv read independently of the reader.

⚠️ tests/test_visium.py skips until someone regenerates the data artifact by running the Prepare test data workflow, as its header describes. It was verified locally against that dataset: with readers/visium.py at d2664fa the reader raises the TypeError of #417 and all nine fail (one failure, eight fixture errors); on this branch all nine pass.

New tests, each verified to fail without its fix:

  • tests/test_visium.py — nine tests on real data, the regression above among them.
  • tests/test_merscope.py_get_reader with a rioxarray on sys.path that raises on import.
  • tests/test_dbit.py_check_path with no directory raises instead of searching the cwd; an explicitly given file still works.
  • tests/test_macsima.pymacsima() on a copy of OMAP10_small with one real tif truncated to 200 bytes, asserting both that the reader completes and that the warning names the file it skipped.

Not addressed here

Listed in #417, left alone deliberately: the xenium cell_polys is not None dead branch, the curio INSTANCE_KEY/index ordering, merscope's wrong _get_reader return annotation, the MIN_VERSION = (3, 13) gate that skips nine reader tests on the 3.12 CI leg, and four pre-existing bugs noticed nearby (curio's discarded var.join, the missing break in macsima's physical-size loop, codex's always-true format detection and its two missing path / prefixes, dbit's write to a view).

The two open questions on the macsima padding feature (#371) are now #419, and still want a maintainer with real data: whether the plane positions are µm rather than pixels, and that the whole translation path is uncovered because the OMAP test data has position_x = None.

Verification

The four touched test files — tests/test_visium.py, tests/test_merscope.py, tests/test_dbit.py, tests/test_macsima.py — are 74 passed locally, with the datasets on disk (Python 3.13, spatialdata 0.8.1.dev, anndata 0.13.3, pandas 2.3.3, tifffile 2025.12.20). The rest of the suite runs in CI.

prek run --all-files passes, mypy src tests included.

LucaMarconato and others added 5 commits September 9, 2026 13:20
`_get_reader()` auto-detection was changed in #411 from a real `import
rioxarray` to `importlib.util.find_spec("rioxarray")`. `find_spec` only
reports whether the module can be *found*; importing it can still fail,
which is what a `rioxarray` with a broken `rasterio` looks like.

Before, that raised `ModuleNotFoundError` and the reader silently used
the `dask_image` backend. Now the rioxarray backend is selected, and its
own internal import turns the failure into "Using rioxarray backend
requires to install the rioxarray library", which misdescribes the cause
and leaves the user with no working backend.

Use `importlib.import_module()` so the import is actually attempted
(a plain `import rioxarray` would be flagged as unused), and catch
`ImportError` rather than only `ModuleNotFoundError`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#411 replaced

    if path is not None:
        path = Path(path)
        if not os.path.isdir(path): raise FileNotFoundError(...)

with `path = Path() if path is None else Path(path)`, to drop a
`# type: ignore` on the `_check_path(path=path)` calls. `Path()` is `.`,
which is always a directory, so `dbit()` with no `path` now searches the
process's current working directory and reads whatever `.h5ad` and
barcode file happen to be there. Previously that combination raised
(`Path.joinpath(None, ...)` -> `AttributeError`).

Restore the original handling and type `_check_path`'s `path` as
`Path | None`, raising there when a directory search is needed but no
directory was given. `dbit(anndata_path=..., barcode_position=...)`
without a `path` keeps working, as it did before #411.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ositions

Two problems, both of which abort `macsima()` on a folder containing one
truncated TIFF.

`create_sdata()`'s physical-size loop exists to tolerate invalid files
("Iterate over path files, as it may still contain invalid files"), but
#411 narrowed its `except Exception` to
`(OSError, ValueError, IndexError, NotImplementedError)`. The failure
modes of `ome_types.from_tiff()` are not enumerable: a TIFF with a valid
header truncated before its metadata raises `struct.error`, which is
none of those, and tifffile's own `TiffFileError` has changed base class
between releases.

`MultiChannelImage.from_paths()` has the same problem with
`except ValueError` and, being reached first, is what actually raises
today. That guard predates #411; fixing only one of the two leaves the
reader broken, so both are widened, with a comment explaining why a
blind except is correct here.

Separately, `_get_translations()` truncates the OME plane positions with
`int()`. They are used as `da.pad` widths so they do have to be
integers, but truncation biases every offset towards the origin
(`0.9 -> 0`); round instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plain Visium reader had no test, which is how a hard `TypeError` on
the first call shipped in #411. Add the 10x Space Ranger 2.0.0 CytAssist
FFPE Human Breast Cancer dataset (CC BY 4.0) to the test-data workflow
and assert the property that broke: the circles are the spot coordinates
of the table, in the order of the table, with the radius taken from the
scalefactors. The expected coordinates are read from
`spatial/tissue_positions.csv` independently of the reader.

Only the filtered matrix and the `spatial/` archive are downloaded (~65
MB); the full-resolution tissue image is ~2 GB and `fullres_image_file`
is optional.

The test skips until the data artifact is regenerated by running the
`Prepare test data` workflow, as its header describes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.28571% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.53%. Comparing base (9be1835) to head (46ac6bf).

Files with missing lines Patch % Lines
src/spatialdata_io/readers/dbit.py 33.33% 4 Missing ⚠️
src/spatialdata_io/readers/merscope.py 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #418      +/-   ##
==========================================
+ Coverage   63.13%   63.53%   +0.39%     
==========================================
  Files          26       26              
  Lines        3258     3263       +5     
==========================================
+ Hits         2057     2073      +16     
+ Misses       1201     1190      -11     
Files with missing lines Coverage Δ
src/spatialdata_io/readers/macsima.py 95.53% <100.00%> (ø)
src/spatialdata_io/readers/merscope.py 28.98% <83.33%> (+3.98%) ⬆️
src/spatialdata_io/readers/dbit.py 26.15% <33.33%> (+7.25%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

LucaMarconato and others added 6 commits September 9, 2026 14:24
`space-ranger/latest/resources/visium-example-data` does not exist: only
Visium HD has an `*-example-data` page, and the Space Ranger docs point
non-HD Visium users to the datasets portal. Cite the portal instead.

The dataset URL in the skip message was also a guess, and its slug
belongs to a different dataset (Space Ranger 1.3.0, not the CytAssist
2.0.0 one used here). Point at the download prefix the workflow actually
uses, which is verifiable, and at the workflow itself for the layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The five "Fixed" entries described bugs introduced by the cookiecutter
migration (#411), which landed after v0.7.1 and has never been released.
Reporting them as fixes is misleading: no released version ever had them,
so there is nothing for a user to have hit. They are internal churn within
the template migration that the "Changed" entry already covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reshape the single regression test into a reader test suite in the style of the
xenium, visium_hd and macsima ones: element names and coordinate systems, the
extent of each coordinate system, the two downscaled images, the table and its
annotation, the circles (the former regression test, kept as the check that the
circles are the spot coordinates in the order of the table), the dataset_id
inferred from the counts file, and a CLI roundtrip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whether the plane positions can be used as pixel padding widths at all is a
question for the MACSima maintainers, not something to change in a regression
fix: rounding them only removes the sub-pixel bias of `int()` and leaves the
larger question of the unit open. Tracked in an issue instead.

The widened `except` clauses stay: without them one unreadable file in the
folder aborts the reader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LucaMarconato and others added 3 commits September 9, 2026 15:26
The test only checked that the reader survives the truncated TIFF; it now also
pins the warning that says which file was skipped, so a silent skip fails too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`mypy src tests` follows `tests/data`, one of the two gitignored locations the
test datasets are downloaded to, and type-checks whatever scripts happen to sit
next to the data. CI never sees it — it downloads the artifact to `data/` and the
pre-commit job downloads nothing — but locally the hook fails on files that are
not ours.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@LucaMarconato LucaMarconato changed the title Fix five behaviour changes introduced by the mypy adoption in #411 Fix four behaviour changes introduced by the mypy adoption in #411 Sep 9, 2026
@LucaMarconato
LucaMarconato merged commit 7170020 into main Sep 9, 2026
8 checks passed
@LucaMarconato
LucaMarconato deleted the fix/pr-411-regressions branch September 9, 2026 14:06
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.

Five behaviour changes introduced by the mypy adoption in #411 (visium() is unusable)

2 participants