spatialdata-io 0.1.4__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.flake8 +2 -0
  2. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.github/workflows/test.yaml +3 -3
  3. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.mypy.ini +1 -1
  4. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.pre-commit-config.yaml +3 -3
  5. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.readthedocs.yaml +1 -1
  6. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/CHANGELOG.md +18 -1
  7. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/PKG-INFO +3 -4
  8. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/_version.py +2 -2
  9. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/conf.py +1 -0
  10. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/pyproject.toml +3 -3
  11. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/_constants/_constants.py +4 -3
  12. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/_constants/_enum.py +2 -1
  13. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/_docs.py +2 -1
  14. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/_utils.py +2 -1
  15. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/_utils/_read_10x_h5.py +4 -4
  16. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/_utils/_utils.py +2 -11
  17. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/codex.py +1 -0
  18. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/cosmx.py +3 -2
  19. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/dbit.py +7 -8
  20. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/mcmicro.py +6 -4
  21. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/merscope.py +5 -3
  22. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/steinbock.py +4 -4
  23. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/stereoseq.py +2 -2
  24. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/visium.py +16 -9
  25. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/visium_hd.py +55 -20
  26. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/xenium.py +42 -21
  27. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/tests/test_xenium.py +1 -1
  28. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.bumpversion.cfg +0 -0
  29. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.codecov.yaml +0 -0
  30. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.editorconfig +0 -0
  31. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.github/codecov.yml +0 -0
  32. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.github/workflows/build.yaml +0 -0
  33. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.github/workflows/prepare_test_data.yaml +0 -0
  34. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.github/workflows/release.yaml +0 -0
  35. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/.gitignore +0 -0
  36. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/LICENSE +0 -0
  37. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/README.md +0 -0
  38. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/Makefile +0 -0
  39. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/_static/.gitkeep +0 -0
  40. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/_templates/.gitkeep +0 -0
  41. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/_templates/autosummary/class.rst +0 -0
  42. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/api.md +0 -0
  43. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/changelog.md +0 -0
  44. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/contributing.md +0 -0
  45. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/extensions/.gitkeep +0 -0
  46. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/extensions/typed_returns.py +0 -0
  47. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/index.md +0 -0
  48. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/make.bat +0 -0
  49. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/references.bib +0 -0
  50. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/references.md +0 -0
  51. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/docs/template_usage.md +0 -0
  52. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/__init__.py +0 -0
  53. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/_constants/__init__.py +0 -0
  54. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/converters/__init__.py +0 -0
  55. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/converters/legacy_anndata.py +0 -0
  56. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/experimental/__init__.py +0 -0
  57. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/__init__.py +0 -0
  58. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/_utils/__init__.py +0 -0
  59. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/curio.py +0 -0
  60. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/iss.py +0 -0
  61. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/src/spatialdata_io/readers/seqfish.py +0 -0
  62. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/tests/__init__.py +0 -0
  63. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/tests/converters/__init__.py +0 -0
  64. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/tests/converters/test_legacy_anndata.py +0 -0
  65. {spatialdata_io-0.1.4 → spatialdata_io-0.1.6}/tests/test_basic.py +0 -0
@@ -38,6 +38,8 @@ ignore =
38
38
  D400
39
39
  # First line should be in imperative mood; try rephrasing
40
40
  D401
41
+ # Abstract base class without abstractmethod.
42
+ B024
41
43
  exclude = .git,__pycache__,build,docs/_build,dist
42
44
  per-file-ignores =
43
45
  tests/*: D
@@ -18,7 +18,7 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  matrix:
21
- python: ["3.9", "3.10"]
21
+ python: ["3.10", "3.12"]
22
22
  os: [ubuntu-latest]
23
23
 
24
24
  env:
@@ -52,7 +52,7 @@ jobs:
52
52
  pip install --pre -e ".[dev,test]"
53
53
 
54
54
  - name: Download artifact of test data
55
- if: matrix.python == '3.10'
55
+ if: matrix.python == '3.12'
56
56
  uses: dawidd6/action-download-artifact@v2
57
57
  with:
58
58
  workflow: prepare_test_data.yaml
@@ -60,7 +60,7 @@ jobs:
60
60
  path: ./data
61
61
 
62
62
  - name: List the data directory
63
- if: matrix.python == '3.10'
63
+ if: matrix.python == '3.12'
64
64
  run: |
65
65
  ls -l ./data
66
66
  pwd
@@ -1,5 +1,5 @@
1
1
  [mypy]
2
- python_version = 3.9
2
+ python_version = 3.10
3
3
  plugins = numpy.typing.mypy_plugin
4
4
 
5
5
  ignore_errors = False
@@ -2,8 +2,8 @@ fail_fast: false
2
2
  default_language_version:
3
3
  python: python3
4
4
  default_stages:
5
- - commit
6
- - push
5
+ - pre-commit
6
+ - pre-push
7
7
  minimum_pre_commit_version: 2.16.0
8
8
  repos:
9
9
  - repo: https://github.com/psf/black
@@ -73,7 +73,7 @@ repos:
73
73
  rev: v3.17.0
74
74
  hooks:
75
75
  - id: pyupgrade
76
- args: [--py3-plus, --py39-plus, --keep-runtime-typing]
76
+ args: [--py3-plus, --py310-plus, --keep-runtime-typing]
77
77
  - repo: local
78
78
  hooks:
79
79
  - id: forbid-to-commit
@@ -3,7 +3,7 @@ version: 2
3
3
  build:
4
4
  os: ubuntu-20.04
5
5
  tools:
6
- python: "3.9"
6
+ python: "3.10"
7
7
  sphinx:
8
8
  configuration: docs/conf.py
9
9
  fail_on_warning: true
@@ -8,7 +8,24 @@ and this project adheres to [Semantic Versioning][].
8
8
  [keep a changelog]: https://keepachangelog.com/en/1.0.0/
9
9
  [semantic versioning]: https://semver.org/spec/v2.0.0.html
10
10
 
11
- ## [0.1.4] - xxxx-xx-xx
11
+ ## [0.1.6] - 2024-11-26
12
+
13
+ - (MERSCOPE) added `feature_key` attribute for points (i.e., the `'gene'` column) #210
14
+ - (Visium HD) get transformation matrices even when only images are parsed #215
15
+ - Support for `xarray.DataTree` (which was moved from `datatree.DataTree`) #232
16
+
17
+ ## [0.1.5] - 2024-09-25
18
+
19
+ ### Added
20
+
21
+ - (Xenium) added `dims` parameter for more control in `xenium_aligned_image()`
22
+
23
+ ### Fixed
24
+
25
+ - Passing `rgb=None` to image model parser for both visium and visiumhd, leading to 3-4 channel images being
26
+ interpreted as RGB(A)
27
+ - Fix header bug Visium data #200
28
+ - (Visium HD) Fix path parsing when images are missing #204 #206
12
29
 
13
30
  ## [0.1.4] - 2024-08-07
14
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: spatialdata-io
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: SpatialData IO for common techs
5
5
  Project-URL: Documentation, https://spatialdata-io.readthedocs.io/
6
6
  Project-URL: Source, https://github.com/scverse/spatialdata-io
@@ -36,8 +36,7 @@ License: BSD 3-Clause License
36
36
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
37
37
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
38
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
- License-File: LICENSE
40
- Requires-Python: >=3.9
39
+ Requires-Python: >=3.10
41
40
  Requires-Dist: anndata
42
41
  Requires-Dist: dask-image
43
42
  Requires-Dist: h5py
@@ -48,7 +47,7 @@ Requires-Dist: pyarrow
48
47
  Requires-Dist: readfcs
49
48
  Requires-Dist: scanpy
50
49
  Requires-Dist: scikit-image
51
- Requires-Dist: spatialdata>=0.2.1
50
+ Requires-Dist: spatialdata>=0.2.6
52
51
  Requires-Dist: tifffile>=2023.8.12
53
52
  Provides-Extra: dev
54
53
  Requires-Dist: bump2version; extra == 'dev'
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1.4'
16
- __version_tuple__ = version_tuple = (0, 1, 4)
15
+ __version__ = version = '0.1.6'
16
+ __version_tuple__ = version_tuple = (0, 1, 6)
@@ -90,6 +90,7 @@ source_suffix = {
90
90
  }
91
91
 
92
92
  intersphinx_mapping = {
93
+ "python": ("https://docs.python.org/3", None),
93
94
  "anndata": ("https://anndata.readthedocs.io/en/stable/", None),
94
95
  "spatialdata": ("https://scverse-spatialdata.readthedocs.io/en/latest/", None),
95
96
  "numpy": ("https://numpy.org/doc/stable/", None),
@@ -10,7 +10,7 @@ dynamic= [
10
10
  ]
11
11
  description = "SpatialData IO for common techs"
12
12
  readme = "README.md"
13
- requires-python = ">=3.9"
13
+ requires-python = ">=3.10"
14
14
  license = {file = "LICENSE"}
15
15
  authors = [
16
16
  {name = "scverse"},
@@ -25,7 +25,7 @@ dependencies = [
25
25
  "anndata",
26
26
  "numpy",
27
27
  "scanpy",
28
- "spatialdata>=0.2.1",
28
+ "spatialdata>=0.2.6",
29
29
  "scikit-image",
30
30
  "h5py",
31
31
  "joblib",
@@ -83,7 +83,7 @@ skip_glob = ["docs/*"]
83
83
 
84
84
  [tool.black]
85
85
  line-length = 120
86
- target-version = ['py39']
86
+ target-version = ['py310']
87
87
  include = '\.pyi?$'
88
88
  exclude = '''
89
89
  (
@@ -322,6 +322,7 @@ class MerscopeKeys(ModeEnum):
322
322
  GLOBAL_Z = "global_z"
323
323
  Z_INDEX = "ZIndex"
324
324
  REGION_KEY = "cells_region"
325
+ GENE_KEY = "gene"
325
326
 
326
327
 
327
328
  @unique
@@ -353,9 +354,9 @@ class VisiumHDKeys(ModeEnum):
353
354
  TISSUE_POSITIONS_FILE = "tissue_positions.parquet"
354
355
 
355
356
  # images
356
- IMAGE_HIRES_FILE = "spatial/tissue_hires_image.png"
357
- IMAGE_LOWRES_FILE = "spatial/tissue_lowres_image.png"
358
- IMAGE_CYTASSIST = "spatial/cytassist_image.tiff"
357
+ IMAGE_HIRES_FILE = "tissue_hires_image.png"
358
+ IMAGE_LOWRES_FILE = "tissue_lowres_image.png"
359
+ IMAGE_CYTASSIST = "cytassist_image.tiff"
359
360
 
360
361
  # scalefactors
361
362
  SCALEFACTORS_FILE = "scalefactors_json.json"
@@ -1,7 +1,8 @@
1
1
  from abc import ABC, ABCMeta
2
+ from collections.abc import Callable
2
3
  from enum import Enum, EnumMeta
3
4
  from functools import wraps
4
- from typing import Any, Callable
5
+ from typing import Any
5
6
 
6
7
 
7
8
  class PrettyEnum(Enum):
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Callable
3
4
  from textwrap import dedent
4
- from typing import Any, Callable
5
+ from typing import Any
5
6
 
6
7
 
7
8
  def inject_docs(**kwargs: Any) -> Callable[..., Any]: # noqa: D103
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
  import warnings
5
- from typing import Any, Callable, TypeVar
5
+ from collections.abc import Callable
6
+ from typing import Any, TypeVar
6
7
 
7
8
  RT = TypeVar("RT")
8
9
 
@@ -31,7 +31,7 @@
31
31
  # code below taken from https://github.com/scverse/scanpy/blob/master/scanpy/readwrite.py
32
32
 
33
33
  from pathlib import Path
34
- from typing import Any, Optional, Union
34
+ from typing import Any
35
35
 
36
36
  import h5py
37
37
  import numpy as np
@@ -40,8 +40,8 @@ from spatialdata._logging import logger
40
40
 
41
41
 
42
42
  def _read_10x_h5(
43
- filename: Union[str, Path],
44
- genome: Optional[str] = None,
43
+ filename: str | Path,
44
+ genome: str | None = None,
45
45
  gex_only: bool = True,
46
46
  ) -> AnnData:
47
47
  """
@@ -96,7 +96,7 @@ def _read_10x_h5(
96
96
  return adata
97
97
 
98
98
 
99
- def _read_v3_10x_h5(filename: Union[str, Path], *, start: Optional[Any] = None) -> AnnData:
99
+ def _read_v3_10x_h5(filename: str | Path, *, start: Any | None = None) -> AnnData:
100
100
  """Read hdf5 file from Cell Ranger v3 or later versions."""
101
101
  with h5py.File(str(filename), "r") as f:
102
102
  try:
@@ -3,9 +3,8 @@ from __future__ import annotations
3
3
  import os
4
4
  from collections.abc import Mapping
5
5
  from pathlib import Path
6
- from typing import Any, Optional, Union
6
+ from typing import Any, Union
7
7
 
8
- import numpy as np
9
8
  from anndata import AnnData, read_text
10
9
  from h5py import File
11
10
 
@@ -13,19 +12,11 @@ from spatialdata_io.readers._utils._read_10x_h5 import _read_10x_h5
13
12
 
14
13
  PathLike = Union[os.PathLike, str] # type:ignore[type-arg]
15
14
 
16
- try:
17
- from numpy.typing import NDArray
18
-
19
- NDArrayA = NDArray[Any]
20
- except (ImportError, TypeError):
21
- NDArray = np.ndarray # type: ignore[misc]
22
- NDArrayA = np.ndarray # type: ignore[misc]
23
-
24
15
 
25
16
  def _read_counts(
26
17
  path: str | Path,
27
18
  counts_file: str,
28
- library_id: Optional[str] = None,
19
+ library_id: str | None = None,
29
20
  **kwargs: Any,
30
21
  ) -> tuple[AnnData, str]:
31
22
  path = Path(path)
@@ -80,6 +80,7 @@ def codex(
80
80
  "images": Image2DModel.parse(
81
81
  image,
82
82
  scale_factors=[2, 2],
83
+ rgb=None,
83
84
  )
84
85
  }
85
86
  sdata = SpatialData(images=images, shapes={str(region): shapes}, table=table)
@@ -5,7 +5,7 @@ import re
5
5
  from collections.abc import Mapping
6
6
  from pathlib import Path
7
7
  from types import MappingProxyType
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
  import dask.array as da
11
11
  import numpy as np
@@ -38,7 +38,7 @@ __all__ = ["cosmx"]
38
38
  @inject_docs(cx=CosmxKeys)
39
39
  def cosmx(
40
40
  path: str | Path,
41
- dataset_id: Optional[str] = None,
41
+ dataset_id: str | None = None,
42
42
  transcripts: bool = True,
43
43
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
44
44
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
@@ -200,6 +200,7 @@ def cosmx(
200
200
  "global_only_image": aff,
201
201
  },
202
202
  dims=("y", "x", "c"),
203
+ rgb=None,
203
204
  **image_models_kwargs,
204
205
  )
205
206
  images[f"{fov}_image"] = parsed_im
@@ -4,7 +4,6 @@ import os
4
4
  import re
5
5
  from pathlib import Path
6
6
  from re import Pattern
7
- from typing import Optional, Union
8
7
 
9
8
  import anndata as ad
10
9
  import numpy as np
@@ -27,9 +26,9 @@ def _check_path(
27
26
  path: Path,
28
27
  pattern: Pattern[str],
29
28
  key: DbitKeys,
30
- path_specific: Optional[str | Path] = None,
29
+ path_specific: str | Path | None = None,
31
30
  optional_arg: bool = False,
32
- ) -> tuple[Union[Path, None], bool]:
31
+ ) -> tuple[Path | None, bool]:
33
32
  """
34
33
  Check that the path is valid and match a regex pattern.
35
34
 
@@ -218,11 +217,11 @@ def _xy2edges(xy: list[int], scale: float = 1.0, border: bool = True, border_sca
218
217
 
219
218
  @inject_docs(vx=DbitKeys)
220
219
  def dbit(
221
- path: Optional[str | Path] = None,
222
- anndata_path: Optional[str] = None,
223
- barcode_position: Optional[str] = None,
224
- image_path: Optional[str] = None,
225
- dataset_id: Optional[str] = None,
220
+ path: str | Path | None = None,
221
+ anndata_path: str | None = None,
222
+ barcode_position: str | None = None,
223
+ image_path: str | None = None,
224
+ dataset_id: str | None = None,
226
225
  border: bool = True,
227
226
  border_scale: float = 1,
228
227
  ) -> SpatialData:
@@ -100,7 +100,7 @@ def mcmicro(
100
100
  assert len(samples) == 1
101
101
  data = imread(samples[0], **imread_kwargs)
102
102
  # , scale_factors=[2, 2]
103
- data = Image2DModel.parse(data, transformations=_get_transformation(), **image_models_kwargs)
103
+ data = Image2DModel.parse(data, transformations=_get_transformation(), rgb=None, **image_models_kwargs)
104
104
  images["tma_map"] = data
105
105
 
106
106
  image_dir = path / McmicroKeys.IMAGES_DIR_TMA
@@ -116,7 +116,7 @@ def mcmicro(
116
116
  image_id = core_id
117
117
 
118
118
  data = imread(sample, **imread_kwargs)
119
- data = Image2DModel.parse(data, c_coords=marker_names, **image_models_kwargs)
119
+ data = Image2DModel.parse(data, c_coords=marker_names, rgb=None, **image_models_kwargs)
120
120
  transformations = _get_transformation(
121
121
  tma=int(core_id) if tma else None, tma_centroids=tma_centroids, raster_data=data
122
122
  )
@@ -131,7 +131,9 @@ def mcmicro(
131
131
  raw_name = raw_image.with_name(raw_image.stem).with_suffix("").stem
132
132
 
133
133
  data = imread(raw_image, **imread_kwargs)
134
- images[raw_name] = Image2DModel.parse(data, transformations={raw_name: Identity()}, **image_models_kwargs)
134
+ images[raw_name] = Image2DModel.parse(
135
+ data, transformations={raw_name: Identity()}, rgb=None, **image_models_kwargs
136
+ )
135
137
 
136
138
  illumination_dir = path / McmicroKeys.ILLUMINATION_DIR
137
139
  if illumination_dir.exists():
@@ -143,7 +145,7 @@ def mcmicro(
143
145
 
144
146
  data = imread(illumination_image, **imread_kwargs)
145
147
  images[illumination_name] = Image2DModel.parse(
146
- data, transformations={raw_name: Identity()}, **image_models_kwargs
148
+ data, transformations={raw_name: Identity()}, rgb=None, **image_models_kwargs
147
149
  )
148
150
 
149
151
  samples_labels = list((path / McmicroKeys.LABELS_DIR).glob("*/*" + McmicroKeys.IMAGE_SUFFIX))
@@ -2,10 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  import warnings
5
- from collections.abc import Mapping
5
+ from collections.abc import Callable, Mapping
6
6
  from pathlib import Path
7
7
  from types import MappingProxyType
8
- from typing import Any, Callable, Literal
8
+ from typing import Any, Literal
9
9
 
10
10
  import anndata
11
11
  import dask.dataframe as dd
@@ -273,7 +273,7 @@ def _rioxarray_load_merscope(
273
273
  dim="c",
274
274
  )
275
275
 
276
- return Image2DModel.parse(im, c_coords=stainings, **image_models_kwargs)
276
+ return Image2DModel.parse(im, c_coords=stainings, rgb=None, **image_models_kwargs)
277
277
 
278
278
 
279
279
  def _dask_image_load_merscope(
@@ -292,6 +292,7 @@ def _dask_image_load_merscope(
292
292
  im,
293
293
  dims=("c", "y", "x"),
294
294
  c_coords=stainings,
295
+ rgb=None,
295
296
  **image_models_kwargs,
296
297
  )
297
298
 
@@ -302,6 +303,7 @@ def _get_points(transcript_path: Path, transformations: dict[str, BaseTransforma
302
303
  transcript_df,
303
304
  coordinates={"x": MerscopeKeys.GLOBAL_X, "y": MerscopeKeys.GLOBAL_Y},
304
305
  transformations=transformations,
306
+ feature_key=MerscopeKeys.GENE_KEY,
305
307
  )
306
308
  transcripts["gene"] = transcripts["gene"].astype("category")
307
309
  return transcripts
@@ -4,7 +4,7 @@ import os
4
4
  from collections.abc import Mapping
5
5
  from pathlib import Path
6
6
  from types import MappingProxyType
7
- from typing import Any, Literal, Union
7
+ from typing import Any, Literal
8
8
 
9
9
  import anndata as ad
10
10
  from dask_image.imread import imread
@@ -95,9 +95,9 @@ def _get_images(
95
95
  sample: str,
96
96
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
97
97
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
98
- ) -> Union[SpatialImage, MultiscaleSpatialImage]:
98
+ ) -> SpatialImage | MultiscaleSpatialImage:
99
99
  image = imread(path / SteinbockKeys.IMAGES_DIR / f"{sample}{SteinbockKeys.IMAGE_SUFFIX}", **imread_kwargs)
100
- return Image2DModel.parse(data=image, transformations={sample: Identity()}, **image_models_kwargs)
100
+ return Image2DModel.parse(data=image, transformations={sample: Identity()}, rgb=None, **image_models_kwargs)
101
101
 
102
102
 
103
103
  def _get_labels(
@@ -106,6 +106,6 @@ def _get_labels(
106
106
  labels_kind: str,
107
107
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
108
108
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
109
- ) -> Union[SpatialImage, MultiscaleSpatialImage]:
109
+ ) -> SpatialImage | MultiscaleSpatialImage:
110
110
  image = imread(path / labels_kind / f"{sample}{SteinbockKeys.LABEL_SUFFIX}", **imread_kwargs).squeeze()
111
111
  return Labels2DModel.parse(data=image, transformations={sample: Identity()}, **image_models_kwargs)
@@ -5,7 +5,7 @@ import re
5
5
  from collections.abc import Mapping
6
6
  from pathlib import Path
7
7
  from types import MappingProxyType
8
- from typing import Any, Union
8
+ from typing import Any
9
9
 
10
10
  import anndata as ad
11
11
  import h5py
@@ -29,7 +29,7 @@ __all__ = ["stereoseq"]
29
29
  @inject_docs(xx=SK)
30
30
  def stereoseq(
31
31
  path: str | Path,
32
- dataset_id: Union[str, None] = None,
32
+ dataset_id: str | None = None,
33
33
  read_square_bin: bool = True,
34
34
  optional_tif: bool = False,
35
35
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
@@ -4,7 +4,6 @@ import json
4
4
  import os
5
5
  import re
6
6
  from collections.abc import Mapping
7
- from functools import partial
8
7
  from pathlib import Path
9
8
  from types import MappingProxyType
10
9
  from typing import Any
@@ -138,7 +137,6 @@ def visium(
138
137
  if (path / "spatial" / VisiumKeys.SPOTS_FILE_1).exists() or (
139
138
  tissue_positions_file is not None and str(VisiumKeys.SPOTS_FILE_1) in str(tissue_positions_file)
140
139
  ):
141
- read_coords = partial(pd.read_csv, header=None, index_col=0)
142
140
  tissue_positions_file = (
143
141
  path / "spatial" / VisiumKeys.SPOTS_FILE_1
144
142
  if tissue_positions_file is None
@@ -147,7 +145,6 @@ def visium(
147
145
  elif (path / "spatial" / VisiumKeys.SPOTS_FILE_2).exists() or (
148
146
  tissue_positions_file is not None and str(VisiumKeys.SPOTS_FILE_2) in str(tissue_positions_file)
149
147
  ):
150
- read_coords = partial(pd.read_csv, header=0, index_col=0)
151
148
  tissue_positions_file = (
152
149
  path / "spatial" / VisiumKeys.SPOTS_FILE_2
153
150
  if tissue_positions_file is None
@@ -155,11 +152,18 @@ def visium(
155
152
  )
156
153
  else:
157
154
  raise ValueError(f"Cannot find `tissue_positions` file in `{path}`.")
158
- coords = read_coords(tissue_positions_file)
155
+ coords = pd.read_csv(tissue_positions_file, header=None, index_col=0)
159
156
 
160
- # to handle spaceranger_version < 2.0.0, where no column names are provided
161
- if "in_tissue" not in coords.columns:
162
- coords.columns = ["in_tissue", "array_row", "array_col", "pxl_row_in_fullres", "pxl_col_in_fullres"]
157
+ if tissue_positions_file.name == VisiumKeys.SPOTS_FILE_1:
158
+ # spaceranger_version < 2.0.0 but header detected: https://github.com/scverse/spatialdata-io/issues/146
159
+ if "in_tissue" in coords.iloc[0].tolist():
160
+ coords = pd.read_csv(tissue_positions_file, header=0, index_col=0)
161
+ else:
162
+ assert "in_tissue" not in coords.columns
163
+ coords.columns = ["in_tissue", "array_row", "array_col", "pxl_row_in_fullres", "pxl_col_in_fullres"]
164
+ else:
165
+ assert tissue_positions_file.name == VisiumKeys.SPOTS_FILE_2
166
+ coords = pd.read_csv(tissue_positions_file, header=0, index_col=0)
163
167
 
164
168
  adata.obs = pd.merge(adata.obs, coords, how="left", left_index=True, right_index=True)
165
169
  coords = adata.obs[[VisiumKeys.SPOTS_X, VisiumKeys.SPOTS_Y]].values
@@ -217,6 +221,7 @@ def visium(
217
221
  full_image,
218
222
  scale_factors=[2, 2, 2, 2],
219
223
  transformations={"global": transform_original},
224
+ rgb=None,
220
225
  **image_models_kwargs,
221
226
  )
222
227
  else:
@@ -226,13 +231,15 @@ def visium(
226
231
  image_hires = imread(path / VisiumKeys.IMAGE_HIRES_FILE, **imread_kwargs).squeeze().transpose(2, 0, 1)
227
232
  image_hires = DataArray(image_hires, dims=("c", "y", "x"))
228
233
  images[dataset_id + "_hires_image"] = Image2DModel.parse(
229
- image_hires, transformations={"downscaled_hires": Identity()}
234
+ image_hires, transformations={"downscaled_hires": Identity()}, rgb=None
230
235
  )
231
236
  if (path / VisiumKeys.IMAGE_LOWRES_FILE).exists():
232
237
  image_lowres = imread(path / VisiumKeys.IMAGE_LOWRES_FILE, **imread_kwargs).squeeze().transpose(2, 0, 1)
233
238
  image_lowres = DataArray(image_lowres, dims=("c", "y", "x"))
234
239
  images[dataset_id + "_lowres_image"] = Image2DModel.parse(
235
- image_lowres, transformations={"downscaled_lowres": Identity()}
240
+ image_lowres,
241
+ transformations={"downscaled_lowres": Identity()},
242
+ rgb=None,
236
243
  )
237
244
 
238
245
  return SpatialData(images=images, shapes=shapes, table=table)
@@ -79,22 +79,21 @@ def visium_hd(
79
79
  image_models_kwargs
80
80
  Keyword arguments for :class:`spatialdata.models.Image2DModel`.
81
81
  anndata_kwargs
82
- Keyword arguments for :func:`anndata.read_h5ad`.
82
+ Keyword arguments for :func:`anndata.io.read_h5ad`.
83
83
 
84
84
  Returns
85
85
  -------
86
86
  SpatialData object for the Visium HD data.
87
87
  """
88
88
  path = Path(path)
89
+ all_files = [file for file in path.rglob("*") if file.is_file()]
89
90
  tables = {}
90
91
  shapes = {}
91
92
  images: dict[str, Any] = {}
92
93
 
93
94
  if dataset_id is None:
94
95
  dataset_id = _infer_dataset_id(path)
95
- filename_prefix = f"{dataset_id}_"
96
- else:
97
- filename_prefix = ""
96
+ filename_prefix = f"{dataset_id}_"
98
97
 
99
98
  def load_image(path: Path, suffix: str, scale_factors: list[int] | None = None) -> None:
100
99
  _load_image(
@@ -108,7 +107,6 @@ def visium_hd(
108
107
  )
109
108
 
110
109
  metadata, hd_layout = _parse_metadata(path, filename_prefix)
111
- transform_matrices = _get_transform_matrices(metadata, hd_layout)
112
110
  file_format = hd_layout[VisiumHDKeys.FILE_FORMAT]
113
111
  if file_format != "1.0":
114
112
  warnings.warn(
@@ -127,8 +125,12 @@ def visium_hd(
127
125
  ]
128
126
  )
129
127
 
130
- if VisiumHDKeys.BINNED_OUTPUTS in os.listdir(path):
131
- path_bins = path / VisiumHDKeys.BINNED_OUTPUTS
128
+ all_path_bins = [path_bin for path_bin in all_files if VisiumHDKeys.BINNED_OUTPUTS in str(path_bin)]
129
+ if len(all_path_bins) != 0:
130
+ path_bins_parts = all_path_bins[
131
+ -1
132
+ ].parts # just choosing last one here as users might have tar file which would be first
133
+ path_bins = Path(*path_bins_parts[: path_bins_parts.index(VisiumHDKeys.BINNED_OUTPUTS) + 1])
132
134
  else:
133
135
  path_bins = path
134
136
  all_bin_sizes = _get_bins(path_bins)
@@ -266,15 +268,26 @@ def visium_hd(
266
268
  fullres_image_filenames = [
267
269
  f for f in os.listdir(path_fullres) if os.path.isfile(os.path.join(path_fullres, f))
268
270
  ]
269
- if len(fullres_image_filenames) > 1:
270
- warnings.warn(
271
- f"Multiple files found in {path_fullres}, using the first one: {fullres_image_filenames[0]}. Please"
272
- " specify the path to the full resolution image manually using the `fullres_image_file` argument.",
273
- UserWarning,
274
- stacklevel=2,
275
- )
276
- fullres_image_filename = fullres_image_filenames[0]
277
- fullres_image_file = path_fullres / fullres_image_filename
271
+ fullres_image_paths = [path_fullres / image_filename for image_filename in fullres_image_filenames]
272
+ elif list((path_fullres := (path / f"{filename_prefix}tissue_image")).parent.glob(f"{path_fullres.name}.*")):
273
+ fullres_image_paths = list(path_fullres.parent.glob(f"{path_fullres.name}.*"))
274
+ else:
275
+ fullres_image_paths = []
276
+ if len(fullres_image_paths) > 1:
277
+ warnings.warn(
278
+ f"Multiple files found in {path_fullres}, using the first one: {fullres_image_paths[0].stem}. Please"
279
+ " specify the path to the full resolution image manually using the `fullres_image_file` argument.",
280
+ UserWarning,
281
+ stacklevel=2,
282
+ )
283
+ if len(fullres_image_paths) == 0:
284
+ warnings.warn(
285
+ "No full resolution image found. If incorrect, please specify the path in the "
286
+ "`fullres_image_file` parameter when calling the `visium_hd` reader function.",
287
+ UserWarning,
288
+ stacklevel=2,
289
+ )
290
+ fullres_image_file = fullres_image_paths[0] if len(fullres_image_paths) > 0 else None
278
291
 
279
292
  if fullres_image_file is not None:
280
293
  load_image(
@@ -284,8 +297,15 @@ def visium_hd(
284
297
  )
285
298
 
286
299
  # hires image
300
+ hires_image_path = [path for path in all_files if VisiumHDKeys.IMAGE_HIRES_FILE in str(path)]
301
+ if len(hires_image_path) == 0:
302
+ warnings.warn(
303
+ f"No image path found containing the hires image: {VisiumHDKeys.IMAGE_HIRES_FILE}",
304
+ UserWarning,
305
+ stacklevel=2,
306
+ )
287
307
  load_image(
288
- path=path / VisiumHDKeys.IMAGE_HIRES_FILE,
308
+ path=hires_image_path[0],
289
309
  suffix="_hires_image",
290
310
  )
291
311
  set_transformation(
@@ -295,8 +315,15 @@ def visium_hd(
295
315
  )
296
316
 
297
317
  # lowres image
318
+ lowres_image_path = [path for path in all_files if VisiumHDKeys.IMAGE_LOWRES_FILE in str(path)]
319
+ if len(lowres_image_path) == 0:
320
+ warnings.warn(
321
+ f"No image path found containing the lowres image: {VisiumHDKeys.IMAGE_LOWRES_FILE}",
322
+ UserWarning,
323
+ stacklevel=2,
324
+ )
298
325
  load_image(
299
- path=path / VisiumHDKeys.IMAGE_LOWRES_FILE,
326
+ path=lowres_image_path[0],
300
327
  suffix="_lowres_image",
301
328
  )
302
329
  set_transformation(
@@ -306,12 +333,20 @@ def visium_hd(
306
333
  )
307
334
 
308
335
  # cytassist image
336
+ cytassist_path = [path for path in all_files if VisiumHDKeys.IMAGE_CYTASSIST in str(path)]
337
+ if len(cytassist_path) == 0:
338
+ warnings.warn(
339
+ f"No image path found containing the cytassist image: {VisiumHDKeys.IMAGE_CYTASSIST}",
340
+ UserWarning,
341
+ stacklevel=2,
342
+ )
309
343
  if load_all_images:
310
344
  load_image(
311
- path=path / VisiumHDKeys.IMAGE_CYTASSIST,
345
+ path=cytassist_path[0],
312
346
  suffix="_cytassist_image",
313
347
  )
314
348
  image = images[dataset_id + "_cytassist_image"]
349
+ transform_matrices = _get_transform_matrices(metadata, hd_layout)
315
350
  affine0 = transform_matrices["cytassist_colrow_to_spot_colrow"]
316
351
  affine1 = transform_matrices["spot_colrow_to_microscope_colrow"]
317
352
  set_transformation(image, Sequence([affine0, affine1]), "global")
@@ -352,7 +387,7 @@ def _load_image(
352
387
  # dask_image doesn't recognize .btf automatically and imageio v3 throws error due to pixel limit -> use imageio v2
353
388
  data = imread2(path, **imread_kwargs).squeeze().transpose(2, 0, 1)
354
389
  image = DataArray(data, dims=("c", "y", "x"))
355
- parsed = Image2DModel.parse(image, scale_factors=scale_factors, **image_models_kwargs)
390
+ parsed = Image2DModel.parse(image, scale_factors=scale_factors, rgb=None, **image_models_kwargs)
356
391
  images[dataset_id + suffix] = parsed
357
392
  else:
358
393
  warnings.warn(f"File {path} does not exist, skipping it.", UserWarning, stacklevel=2)
@@ -10,7 +10,7 @@ import zipfile
10
10
  from collections.abc import Mapping
11
11
  from pathlib import Path
12
12
  from types import MappingProxyType
13
- from typing import Any, Optional
13
+ from typing import Any
14
14
 
15
15
  import dask.array as da
16
16
  import numpy as np
@@ -24,10 +24,8 @@ from dask.dataframe import read_parquet
24
24
  from dask_image.imread import imread
25
25
  from geopandas import GeoDataFrame
26
26
  from joblib import Parallel, delayed
27
- from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
28
27
  from pyarrow import Table
29
28
  from shapely import Polygon
30
- from spatial_image import SpatialImage
31
29
  from spatialdata import SpatialData
32
30
  from spatialdata._core.query.relational_query import get_element_instances
33
31
  from spatialdata._types import ArrayLike
@@ -39,6 +37,7 @@ from spatialdata.models import (
39
37
  TableModel,
40
38
  )
41
39
  from spatialdata.transformations.transformations import Affine, Identity, Scale
40
+ from xarray import DataArray, DataTree
42
41
 
43
42
  from spatialdata_io._constants._constants import XeniumKeys
44
43
  from spatialdata_io._docs import inject_docs
@@ -111,7 +110,9 @@ def xenium(
111
110
  morphology_focus
112
111
  Whether to read the morphology focus image.
113
112
  aligned_images
114
- Whether to also parse, when available, additional H&E or IF aligned images.
113
+ Whether to also parse, when available, additional H&E or IF aligned images. For more control over the aligned
114
+ images being read, in particular, to specify the axes of the aligned images, please set this parameter to
115
+ `False` and use the `xenium_aligned_image` function directly.
115
116
  cells_table
116
117
  Whether to read the cell annotations in the `AnnData` table.
117
118
  n_jobs
@@ -362,7 +363,7 @@ def _decode_cell_id_column(cell_id_column: pd.Series) -> pd.Series:
362
363
 
363
364
 
364
365
  def _get_polygons(
365
- path: Path, file: str, specs: dict[str, Any], n_jobs: int, idx: Optional[ArrayLike] = None
366
+ path: Path, file: str, specs: dict[str, Any], n_jobs: int, idx: ArrayLike | None = None
366
367
  ) -> GeoDataFrame:
367
368
  def _poly(arr: ArrayLike) -> Polygon:
368
369
  return Polygon(arr[:-1])
@@ -513,6 +514,7 @@ def _get_points(path: Path, specs: dict[str, Any]) -> Table:
513
514
  feature_key=XeniumKeys.FEATURE_NAME,
514
515
  instance_key=XeniumKeys.CELL_ID,
515
516
  transformations={"global": transform},
517
+ sort=True,
516
518
  )
517
519
  return points
518
520
 
@@ -550,7 +552,7 @@ def _get_images(
550
552
  file: str,
551
553
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
552
554
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
553
- ) -> SpatialImage | MultiscaleSpatialImage:
555
+ ) -> DataArray | DataTree:
554
556
  image = imread(path / file, **imread_kwargs)
555
557
  if "c_coords" in image_models_kwargs and "dummy" in image_models_kwargs["c_coords"]:
556
558
  # Napari currently interprets 4 channel images as RGB; a series of PRs to fix this is almost ready but they will
@@ -559,7 +561,7 @@ def _get_images(
559
561
  # let's add a dummy channel as a temporary workaround.
560
562
  image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
561
563
  return Image2DModel.parse(
562
- image, transformations={"global": Identity()}, dims=("c", "y", "x"), **image_models_kwargs
564
+ image, transformations={"global": Identity()}, dims=("c", "y", "x"), rgb=None, **image_models_kwargs
563
565
  )
564
566
 
565
567
 
@@ -567,7 +569,7 @@ def _add_aligned_images(
567
569
  path: Path,
568
570
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
569
571
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
570
- ) -> dict[str, MultiscaleSpatialImage]:
572
+ ) -> dict[str, DataTree]:
571
573
  """Discover and parse aligned images."""
572
574
  images = {}
573
575
  ome_tif_files = list(path.glob("*.ome.tif"))
@@ -598,7 +600,8 @@ def xenium_aligned_image(
598
600
  alignment_file: str | Path | None,
599
601
  imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
600
602
  image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
601
- ) -> MultiscaleSpatialImage:
603
+ dims: tuple[str, ...] | None = None,
604
+ ) -> DataTree:
602
605
  """
603
606
  Read an image aligned to a Xenium dataset, with an optional alignment file.
604
607
 
@@ -610,6 +613,12 @@ def xenium_aligned_image(
610
613
  Path to the alignment file, if not passed it is assumed that the image is aligned.
611
614
  image_models_kwargs
612
615
  Keyword arguments to pass to the image models.
616
+ dims
617
+ Dimensions of the image (tuple of axes names); valid strings are "c", "x" and "y". If not passed, the function
618
+ will try to infer the dimensions from the image shape. Please use this argument when the default behavior fails.
619
+ Example: for an image with shape (1, y, 1, x, 3), use dims=("anystring", "y", "dummy", "x", "c"). Values that
620
+ are not "c", "x" or "y" are considered dummy dimensions and will be squeezed (the data must have len 1 for
621
+ those axes).
613
622
 
614
623
  Returns
615
624
  -------
@@ -627,19 +636,28 @@ def xenium_aligned_image(
627
636
  # In fact, it could be that the len(image.shape) == 4 has actually dimes (1, x, y, c) and not (1, y, x, c). This is
628
637
  # not a problem because the transformation is constructed to be consistent, but if is the case, the data orientation
629
638
  # would be transposed compared to the original image, not ideal.
630
- if len(image.shape) == 4:
631
- assert image.shape[0] == 1
632
- assert image.shape[-1] == 3
633
- image = image.squeeze(0)
634
- dims = ("y", "x", "c")
639
+ if dims is None:
640
+ if len(image.shape) == 4:
641
+ assert image.shape[0] == 1
642
+ assert image.shape[-1] == 3
643
+ image = image.squeeze(0)
644
+ dims = ("y", "x", "c")
645
+ else:
646
+ assert len(image.shape) == 3
647
+ assert image.shape[0] in [3, 4]
648
+ if image.shape[0] == 4:
649
+ # as explained before in _get_images(), we need to add a dummy channel until we support 4-channel images as
650
+ # non-RGBA images in napari
651
+ image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
652
+ dims = ("c", "y", "x")
635
653
  else:
636
- assert len(image.shape) == 3
637
- assert image.shape[0] in [3, 4]
638
- if image.shape[0] == 4:
639
- # as explained before in _get_images(), we need to add a dummy channel until we support 4-channel images as
640
- # non-RGBA images in napari
641
- image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
642
- dims = ("c", "y", "x")
654
+ logging.info(f"Image has shape {image.shape}, parsing with dims={dims}.")
655
+ image = DataArray(image, dims=dims)
656
+ # squeeze spurious dimensions away
657
+ to_squeeze = [dim for dim in dims if dim not in ["c", "x", "y"]]
658
+ dims = tuple(dim for dim in dims if dim in ["c", "x", "y"])
659
+ for dim in to_squeeze:
660
+ image = image.squeeze(dim)
643
661
 
644
662
  if alignment_file is None:
645
663
  transformation = Identity()
@@ -762,3 +780,6 @@ def prefix_suffix_uint32_from_cell_id_str(cell_id_str: ArrayLike) -> tuple[Array
762
780
  cell_id_prefix = [int(x, 16) for x in cell_id_prefix_hex]
763
781
 
764
782
  return np.array(cell_id_prefix, dtype=np.uint32), np.array(dataset_suffix_int)
783
+
784
+
785
+ ##
@@ -46,7 +46,7 @@ def test_roundtrip_with_data_limits() -> None:
46
46
  # pointing to "data".
47
47
  # The GitHub workflow "prepare_test_data.yaml" takes care of downloading the datasets and uploading an artifact for the
48
48
  # tests to use
49
- @pytest.mark.skipif(sys.version_info < (3, 10), reason="Test requires Python 3.10 or higher")
49
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="Test requires Python 3.10 or higher")
50
50
  @pytest.mark.parametrize(
51
51
  "dataset,expected",
52
52
  [
File without changes
File without changes