scverse / scverse/squidpy

`spatial_scatter` uses inconsistent `spatial_key` for image validation and loading

Open
#1,269 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
598
Forks
121
Avg merge
3d 11h
Merged PRs (30d)
3

Description

Report

When using sq.pl.spatial_scatter() with a custom spatial_key, I noticed inconsistent behavior in how background images are validated and loaded.

My AnnData contains two coordinate systems:

adata.obsm["spatial"]
adata.obsm["spatial_trans"]

I want to use spatial_trans for plotting:

sq.pl.spatial_scatter(
    adata,
    spatial_key="spatial_trans",
    img="hires",
    img_res_key="hires",
    ...
)

The issue appears to be in src/squidpy/pl/_spatial_utils.py, around lines 166–173:

image_mapping = Key.uns.library_mapping(adata, spatial_key, Key.uns.image_key, library_id)

if img_res_key is None:
    img_res_key = _get_unique_map(image_mapping)[0]
elif img_res_key not in _get_unique_map(image_mapping):
    raise KeyError(
        f"Image key: `{img_res_key}` does not exist. Available image keys: `{image_mapping.values()}`"
    )

img = [adata.uns[Key.uns.spatial][i][Key.uns.image_key][img_res_key] for i in library_id]

Here, the image validation around line 166 uses:

adata.uns[spatial_key][library_id]["images"]

while the actual image loading around line 173 uses:

adata.uns["spatial"][library_id]["images"]

This leads to inconsistent behavior whenever spatial_key != "spatial":

  • If images are stored under adata.uns["spatial_trans"], validation succeeds, but image loading fails because the image is actually loaded from adata.uns["spatial"].
  • If images are stored under adata.uns["spatial"], image loading would be correct, but validation fails first because library_mapping() checks adata.uns["spatial_trans"].

For example, with images stored under adata.uns["spatial"], I get:

KeyError: "`images` not found in `adata.uns['spatial_trans']['library_id'])`
with following `library_id`: ['sham', 'MCAO_1d', 'MCAO_3d', 'MCAO_14d']."

I think either line 166 or line 173 should be changed so that image validation and image loading use the same location.

If the intended structure is that images are always stored under:

adata.uns["spatial"][library_id]["images"]

and spatial_key only specifies the coordinate system in adata.obsm, then line 166 could be changed from:

image_mapping = Key.uns.library_mapping(
    adata, spatial_key, Key.uns.image_key, library_id
)

to:

image_mapping = Key.uns.library_mapping(
    adata, Key.uns.spatial, Key.uns.image_key, library_id
)

Alternatively, if spatial_key is intended to specify both the coordinate system and the corresponding entry in adata.uns, then line 173 should probably use spatial_key instead of Key.uns.spatial.

Either way, making these two lookups consistent should resolve the issue when using custom spatial coordinate keys.

I'd be happy to submit a PR to fix this once the intended behavior of spatial_key is confirmed.

Versions
| Package  | Version   |
| -------- | --------- |
| tifffile | 2026.5.15 |
| scanpy   | 1.12.1    |
| squidpy  | 1.8.1     |
| anndata  | 0.12.16   |
| numpy    | 2.4.6     |

| Dependency               | Version                |
| ------------------------ | ---------------------- |
| stack_data               | 0.6.3                  |
| debugpy                  | 1.8.16                 |
| matplotlib-scalebar      | 0.9.0                  |
| spatialdata              | 0.7.3                  |
| zstandard                | 0.25.0                 |
| MarkupSafe               | 3.0.3                  |
| natsort                  | 8.4.0                  |
| llvmlite                 | 0.47.0                 |
| comm                     | 0.2.3                  |
| lz4                      | 4.4.5                  |
| parso                    | 0.8.7                  |
| rich                     | 15.0.0                 |
| shapely                  | 2.1.2                  |
| lazy-loader              | 0.5                    |
| more-itertools           | 11.1.0                 |
| Pygments                 | 2.20.0                 |
| fast-array-utils         | 1.4.1                  |
| cloudpickle              | 3.1.2                  |
| numcodecs                | 0.16.5                 |
| setuptools               | 82.0.1                 |
| tqdm                     | 4.67.3                 |
| cytoolz                  | 1.1.0                  |
| google-crc32c            | 1.8.0                  |
| python-dateutil          | 2.9.0.post0            |
| imagecodecs              | 2025.11.11             |
| xarray-dataclass         | 3.0.0                  |
| jupyter_core             | 5.9.1                  |
| legacy-api-wrap          | 1.5                    |
| packaging                | 26.2                   |
| matplotlib               | 3.10.9                 |
| pyproj                   | 3.7.2                  |
| zarr                     | 3.2.1                  |
| sparse                   | 0.18.0                 |
| tornado                  | 6.5.5                  |
| validators               | 0.35.0                 |
| numba                    | 0.65.1                 |
| h5py                     | 3.16.0                 |
| platformdirs             | 4.9.6                  |
| pathlib_abc              | 0.5.2                  |
| fsspec                   | 2026.4.0               |
| pillow                   | 12.2.0                 |
| xxhash                   | 3.7.0                  |
| seaborn                  | 0.13.2                 |
| threadpoolctl            | 3.6.0                  |
| dask                     | 2026.1.1               |
| scipy                    | 1.17.1                 |
| donfig                   | 0.8.1.post1            |
| wcwidth                  | 0.2.14                 |
| dask-image               | 2025.11.0              |
| xarray                   | 2026.4.0               |
| pandas                   | 2.3.3                  |
| pooch                    | 1.9.0 (v1.9.0)         |
| PyYAML                   | 6.0.3                  |
| traitlets                | 5.15.0                 |
| scikit-learn             | 1.8.0                  |
| pyarrow                  | 24.0.0                 |
| ipykernel                | 7.2.0                  |
| annsel                   | 0.1.2                  |
| spatial_image            | 1.2.3                  |
| pyzmq                    | 27.1.0                 |
| networkx                 | 3.6.1                  |
| multiscale_spatial_image | 2.0.3                  |
| six                      | 1.17.0                 |
| pyparsing                | 3.3.2                  |
| decorator                | 5.2.1                  |
| narwhals                 | 2.21.2                 |
| session-info2            | 0.4.1                  |
| msgpack                  | 1.1.2                  |
| certifi                  | 2026.6.17 (2026.06.17) |
| geopandas                | 1.1.3                  |
| Jinja2                   | 3.1.6                  |
| charset-normalizer       | 3.4.7                  |
| pure_eval                | 0.2.3                  |
| toolz                    | 1.1.0                  |
| cycler                   | 0.12.1                 |
| psutil                   | 7.2.2                  |
| scverse-misc             | 0.0.7                  |
| jupyter_client           | 8.8.0                  |
| universal_pathlib        | 0.3.10                 |
| scikit-image             | 0.26.0                 |
| ome-zarr                 | 0.15.0                 |
| pytz                     | 2026.2                 |
| typing_extensions        | 4.15.0                 |
| tblib                    | 3.2.2                  |
| executing                | 2.2.1                  |
| ipywidgets               | 8.1.7                  |
| ImageIO                  | 2.37.3                 |
| kiwisolver               | 1.5.0                  |
| joblib                   | 1.5.3                  |
| prompt_toolkit           | 3.0.52                 |
| docrep                   | 0.3.2                  |
| statsmodels              | 0.14.6                 |
| jedi                     | 0.20.0                 |
| asttokens                | 3.0.1                  |
| patsy                    | 1.0.2                  |
| ipython                  | 9.11.0                 |

| Component | Info                                                                           |
| --------- | ------------------------------------------------------------------------------ |
| Python    | 3.12.12 | packaged by conda-forge | (main, Oct 13 2025, 14:34:15) [GCC 14.3.0] |
| OS        | Linux-6.8.0-137-generic-x86_64-with-glibc2.39                                  |
| Updated   | 2026-08-19 09:19                                                             |

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/squidpy/pl/_spatial_utils.py around lines 166–173 and trace how Key.uns.library_mapping validates images versus how the image list is loaded. Confirm the intended relationship between spatial_key and adata.uns storage, then make both lookups consistent and verify that spatial_scatter works with a custom spatial_key and background image.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-visualization
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.