spatialdata-io 0.5.1__tar.gz → 0.7.0__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 (87) hide show
  1. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/prepare_test_data.yaml +10 -1
  2. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.gitignore +10 -2
  3. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.pre-commit-config.yaml +3 -3
  4. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/PKG-INFO +8 -5
  5. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/README.md +1 -1
  6. spatialdata_io-0.7.0/_version.py +24 -0
  7. spatialdata_io-0.7.0/asv.conf.json +23 -0
  8. spatialdata_io-0.7.0/benchmarks/__init__.py +1 -0
  9. spatialdata_io-0.7.0/benchmarks/benchmark_image.py +180 -0
  10. spatialdata_io-0.7.0/benchmarks/benchmark_imports.py +103 -0
  11. spatialdata_io-0.7.0/benchmarks/benchmark_xenium.py +108 -0
  12. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/conf.py +1 -0
  13. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/pyproject.toml +28 -22
  14. spatialdata_io-0.7.0/src/spatialdata_io/__init__.py +100 -0
  15. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/__main__.py +342 -47
  16. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/_constants.py +1 -3
  17. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/legacy_anndata.py +68 -55
  18. spatialdata_io-0.7.0/src/spatialdata_io/readers/_utils/_image.py +151 -0
  19. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/_utils/_utils.py +19 -6
  20. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/codex.py +4 -3
  21. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/cosmx.py +7 -5
  22. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/curio.py +3 -1
  23. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/dbit.py +2 -1
  24. spatialdata_io-0.7.0/src/spatialdata_io/readers/generic.py +237 -0
  25. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/iss.py +3 -1
  26. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/macsima.py +375 -44
  27. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/mcmicro.py +3 -1
  28. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/merscope.py +3 -1
  29. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/seqfish.py +112 -50
  30. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/steinbock.py +27 -2
  31. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/stereoseq.py +2 -2
  32. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/visium.py +3 -2
  33. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/visium_hd.py +61 -24
  34. spatialdata_io-0.7.0/src/spatialdata_io/readers/xenium.py +1128 -0
  35. spatialdata_io-0.7.0/tests/readers/test_utils_image.py +64 -0
  36. spatialdata_io-0.7.0/tests/test_cli_alignment.py +116 -0
  37. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_generic.py +69 -0
  38. spatialdata_io-0.7.0/tests/test_init.py +23 -0
  39. spatialdata_io-0.7.0/tests/test_macsima.py +680 -0
  40. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_seqfish.py +3 -2
  41. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_xenium.py +77 -5
  42. spatialdata_io-0.5.1/_version.py +0 -34
  43. spatialdata_io-0.5.1/src/spatialdata_io/__init__.py +0 -61
  44. spatialdata_io-0.5.1/src/spatialdata_io/readers/_utils/_read_10x_h5.py +0 -144
  45. spatialdata_io-0.5.1/src/spatialdata_io/readers/generic.py +0 -83
  46. spatialdata_io-0.5.1/src/spatialdata_io/readers/xenium.py +0 -850
  47. spatialdata_io-0.5.1/tests/test_basic.py +0 -5
  48. spatialdata_io-0.5.1/tests/test_macsima.py +0 -231
  49. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.bumpversion.cfg +0 -0
  50. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.codecov.yaml +0 -0
  51. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.editorconfig +0 -0
  52. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/codecov.yml +0 -0
  53. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/release.yml +0 -0
  54. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/release.yaml +0 -0
  55. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/test.yaml +0 -0
  56. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.mypy.ini +0 -0
  57. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.readthedocs.yaml +0 -0
  58. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/LICENSE +0 -0
  59. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/Makefile +0 -0
  60. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_static/.gitkeep +0 -0
  61. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_templates/.gitkeep +0 -0
  62. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_templates/autosummary/class.rst +0 -0
  63. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/api.md +0 -0
  64. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/changelog.md +0 -0
  65. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/cli.md +0 -0
  66. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/contributing.md +0 -0
  67. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/extensions/.gitkeep +0 -0
  68. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/extensions/typed_returns.py +0 -0
  69. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/index.md +0 -0
  70. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/make.bat +0 -0
  71. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/references.bib +0 -0
  72. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/references.md +0 -0
  73. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/__init__.py +0 -0
  74. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/_enum.py +0 -0
  75. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_docs.py +0 -0
  76. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_utils.py +0 -0
  77. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/__init__.py +0 -0
  78. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/generic_to_zarr.py +0 -0
  79. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/experimental/__init__.py +0 -0
  80. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/__init__.py +0 -0
  81. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/_utils/__init__.py +0 -0
  82. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/__init__.py +0 -0
  83. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/_utils.py +0 -0
  84. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/conftest.py +0 -0
  85. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/converters/__init__.py +0 -0
  86. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/converters/test_legacy_anndata.py +0 -0
  87. {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_visium_hd.py +0 -0
@@ -4,7 +4,8 @@ on:
4
4
  schedule:
5
5
  - cron: "0 0 1 * *" # run once a month to prevent artifact expiration
6
6
  workflow_dispatch:
7
- # uncomment and adjust the branch name if you need to add new datasets to the artifact
7
+ # Uncomment and adjust the branch name if you need to add new datasets to the artifact.
8
+ # It needs to be a branch in the spatialdata-io origin repository, not from a fork.
8
9
  # push:
9
10
  # branches:
10
11
  # - main
@@ -57,6 +58,14 @@ jobs:
57
58
  # Spatial Genomics seqFISH v2
58
59
  curl -O https://s3.embl.de/spatialdata/raw_data/seqfish-2-test-dataset.zip
59
60
 
61
+ # -------
62
+ # MACSima OMAP datasets are licensed as CC BY 4.0
63
+ # OMAP23 for format v1.x.x
64
+ curl -o OMAP23_small.zip "https://zenodo.org/api/records/18196452/files-archive"
65
+
66
+ # OMAP10 for format v0.x.x
67
+ curl -o OMAP10_small.zip "https://zenodo.org/api/records/18196366/files-archive"
68
+
60
69
  - name: Unzip files
61
70
  run: |
62
71
  cd ./data
@@ -6,6 +6,7 @@ temp/
6
6
 
7
7
  # Compiled files
8
8
  __pycache__/
9
+ .ipynb_checkpoints/
9
10
 
10
11
  # Distribution / packaging
11
12
  /build/
@@ -34,10 +35,17 @@ __pycache__/
34
35
  _version.py
35
36
  node_modules/
36
37
  .code-workspace
38
+ .asv/
39
+ uv.lock
40
+
41
+ # memray report
42
+ *.bin
43
+
44
+ # speedscope report
45
+ profile.speedscope.json
37
46
 
38
47
  # test datasets (e.g. Xenium ones)
39
- # symlinks
40
- data
41
48
  # data folder
42
49
  data/
43
50
  tests/data
51
+ .venv
@@ -7,17 +7,17 @@ default_stages:
7
7
  minimum_pre_commit_version: 2.16.0
8
8
  repos:
9
9
  - repo: https://github.com/rbubley/mirrors-prettier
10
- rev: v3.6.2
10
+ rev: v3.8.3
11
11
  hooks:
12
12
  - id: prettier
13
13
  - repo: https://github.com/astral-sh/ruff-pre-commit
14
- rev: v0.14.1
14
+ rev: v0.15.12
15
15
  hooks:
16
16
  - id: ruff
17
17
  args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]
18
18
  - id: ruff-format
19
19
  - repo: https://github.com/pre-commit/mirrors-mypy
20
- rev: v1.18.2
20
+ rev: v1.20.2
21
21
  hooks:
22
22
  - id: mypy
23
23
  additional_dependencies: [numpy, types-PyYAML]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdata-io
3
- Version: 0.5.1
3
+ Version: 0.7.0
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
@@ -50,8 +50,9 @@ Requires-Dist: pyarrow
50
50
  Requires-Dist: readfcs
51
51
  Requires-Dist: scanpy
52
52
  Requires-Dist: scikit-image
53
- Requires-Dist: spatialdata>=0.2.6
53
+ Requires-Dist: spatialdata>=0.7.3a0
54
54
  Requires-Dist: tifffile>=2023.8.12
55
+ Requires-Dist: xmltodict
55
56
  Provides-Extra: dev
56
57
  Requires-Dist: bump2version; extra == 'dev'
57
58
  Requires-Dist: pre-commit; extra == 'dev'
@@ -63,13 +64,15 @@ Requires-Dist: sphinx-book-theme>=1.0.0; extra == 'doc'
63
64
  Requires-Dist: sphinx-click; extra == 'doc'
64
65
  Requires-Dist: sphinx-copybutton; extra == 'doc'
65
66
  Requires-Dist: sphinx-design; extra == 'doc'
66
- Requires-Dist: sphinx>=4.5; extra == 'doc'
67
+ Requires-Dist: sphinx<9,>=4.5; extra == 'doc'
67
68
  Requires-Dist: sphinxcontrib-bibtex>=1.0.0; extra == 'doc'
68
69
  Provides-Extra: pre
69
- Requires-Dist: spatialdata>=0.4.0rc0; extra == 'pre'
70
+ Requires-Dist: spatialdata>=0.7.3a0; extra == 'pre'
70
71
  Provides-Extra: test
72
+ Requires-Dist: pyarrow!=22; extra == 'test'
71
73
  Requires-Dist: pytest; extra == 'test'
72
74
  Requires-Dist: pytest-cov; extra == 'test'
75
+ Requires-Dist: pytest-mock; extra == 'test'
73
76
  Description-Content-Type: text/markdown
74
77
 
75
78
  ![SpatialData banner](https://github.com/scverse/spatialdata/blob/main/docs/_static/img/spatialdata_horizontal.png?raw=true)
@@ -108,7 +111,7 @@ This package contains reader functions to load common spatial omics formats into
108
111
 
109
112
  Note: all mentioned technologies are registered trademarks of their respective companies.
110
113
 
111
- Please refer to the list of [open Pull Requests](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) for readers that are currently being developed.
114
+ Please refer to the list of [open Pull Requests](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) for readers that are currently being developed. The list of [closed but unmerged PRs](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aunmerged+is%3Aclosed) may also contain useful code from old drafts of readers. Please get in touch if you would like to [contribute](https://github.com/scverse/spatialdata-io/blob/main/docs/contributing.md) and iterate on a PR, or reopen a closed one.
112
115
 
113
116
  ## Known limitations
114
117
 
@@ -34,7 +34,7 @@ This package contains reader functions to load common spatial omics formats into
34
34
 
35
35
  Note: all mentioned technologies are registered trademarks of their respective companies.
36
36
 
37
- Please refer to the list of [open Pull Requests](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) for readers that are currently being developed.
37
+ Please refer to the list of [open Pull Requests](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) for readers that are currently being developed. The list of [closed but unmerged PRs](https://github.com/scverse/spatialdata-io/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aunmerged+is%3Aclosed) may also contain useful code from old drafts of readers. Please get in touch if you would like to [contribute](https://github.com/scverse/spatialdata-io/blob/main/docs/contributing.md) and iterate on a PR, or reopen a closed one.
38
38
 
39
39
  ## Known limitations
40
40
 
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.7.0'
22
+ __version_tuple__ = version_tuple = (0, 7, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": 1,
3
+ "project": "spatialdata-io",
4
+ "project_url": "https://github.com/scverse/spatialdata-io",
5
+ "repo": ".",
6
+ "branches": ["faster-imports", "main"],
7
+ "dvcs": "git",
8
+ "environment_type": "virtualenv",
9
+ "pythons": ["3.13"],
10
+ "build_command": [],
11
+ "install_command": ["python -m pip install {build_dir}[test]"],
12
+ "uninstall_command": ["python -m pip uninstall -y {project}"],
13
+ "env_dir": ".asv/env",
14
+ "results_dir": ".asv/results",
15
+ "html_dir": ".asv/html",
16
+ "benchmark_dir": "benchmarks",
17
+ "hash_length": 8,
18
+ "build_cache_size": 2,
19
+ "install_timeout": 600,
20
+ "repeat": 5,
21
+ "processes": 1,
22
+ "attribute_selection": ["time_*", "peakmem_*"]
23
+ }
@@ -0,0 +1 @@
1
+ # ASV benchmarks for spatialdata-io
@@ -0,0 +1,180 @@
1
+ """Benchmarks for SpatialData IO operations for large images.
2
+
3
+ Instructions:
4
+ See benchmark_xenium.py for instructions.
5
+ """
6
+
7
+ import logging
8
+ import logging.handlers
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import numpy as np
14
+ import tifffile
15
+ from spatialdata import SpatialData
16
+ from spatialdata._logging import logger
17
+ from xarray import DataArray
18
+
19
+ from spatialdata_io import image
20
+
21
+ # =============================================================================
22
+ # CONFIGURATION - Edit these values to match your setup
23
+ # =============================================================================
24
+ # Image dimensions: (channels, height, width)
25
+ IMAGE_SHAPE = (3, 30000, 30000)
26
+ # =============================================================================
27
+
28
+
29
+ class IOBenchmarkImage:
30
+ """Benchmark IO read operations with different parameter combinations."""
31
+
32
+ timeout = 3600
33
+ repeat = 3
34
+ number = 1
35
+ warmup_time = 0
36
+ processes = 1
37
+
38
+ # Parameter combinations: scale_factors, (use_tiff_memmap, compressed), chunks
39
+ # Combinations: (memmap=False, compressed=True), (memmap=False, compressed=False), (memmap=True, compressed=False)
40
+ params = [
41
+ [None, [2, 2]], # scale_factors
42
+ [(False, True), (False, False), (True, False)], # (use_tiff_memmap, compressed)
43
+ [(1, 250, 250), (3, 250, 250)], # chunks
44
+ ]
45
+ param_names = ["scale_factors", "memmap_compressed", "chunks"]
46
+
47
+ # Class-level temp directory for image files (persists across all benchmarks)
48
+ _images_temp_dir: tempfile.TemporaryDirectory[str] | None = None
49
+ _path_read_uncompressed: Path | None = None
50
+ _path_read_compressed: Path | None = None
51
+
52
+ @classmethod
53
+ def _setup_images(cls) -> None:
54
+ """Create fake image data once for all benchmarks."""
55
+ if cls._images_temp_dir is not None:
56
+ return
57
+
58
+ cls._images_temp_dir = tempfile.TemporaryDirectory()
59
+ images_dir = Path(cls._images_temp_dir.name)
60
+ cls._path_read_uncompressed = images_dir / "image_uncompressed.tif"
61
+ cls._path_read_compressed = images_dir / "image_compressed.tif"
62
+
63
+ # Generate fake image data
64
+ rng = np.random.default_rng(42)
65
+ data = rng.integers(0, 255, size=IMAGE_SHAPE, dtype=np.uint8)
66
+
67
+ # Write uncompressed TIFF (memmappable)
68
+ tifffile.imwrite(cls._path_read_uncompressed, data, compression=None)
69
+ # Write compressed TIFF (not memmappable)
70
+ tifffile.imwrite(cls._path_read_compressed, data, compression="zlib")
71
+
72
+ def setup(self, *_: Any) -> None:
73
+ """Set up paths for benchmarking."""
74
+ # Create images once (shared across all benchmark runs)
75
+ self._setup_images()
76
+ self.path_read_uncompressed = self._path_read_uncompressed
77
+ self.path_read_compressed = self._path_read_compressed
78
+
79
+ # Create a separate temp directory for output (cleaned up after each run)
80
+ self._output_temp_dir = tempfile.TemporaryDirectory()
81
+ self.path_write = Path(self._output_temp_dir.name) / "data_benchmark.zarr"
82
+
83
+ def teardown(self, *_: Any) -> None:
84
+ """Clean up output directory after each benchmark run."""
85
+ if hasattr(self, "_output_temp_dir"):
86
+ self._output_temp_dir.cleanup()
87
+
88
+ def _convert_image(
89
+ self, scale_factors: list[int] | None, memmap_compressed: tuple[bool, bool], chunks: tuple[int, ...]
90
+ ) -> SpatialData:
91
+ """Read image data with specified parameters."""
92
+ use_tiff_memmap, compressed = memmap_compressed
93
+ # Select file based on compression setting
94
+ path_read = self.path_read_compressed if compressed else self.path_read_uncompressed
95
+ assert path_read is not None
96
+
97
+ # Capture log messages to verify memmappable warning behavior
98
+ log_capture = logging.handlers.MemoryHandler(capacity=100)
99
+ log_capture.setLevel(logging.WARNING)
100
+ logger.addHandler(log_capture)
101
+ original_propagate = logger.propagate
102
+ logger.propagate = True
103
+
104
+ try:
105
+ im = image(
106
+ input=path_read,
107
+ data_axes=("c", "y", "x"),
108
+ coordinate_system="global",
109
+ use_tiff_memmap=use_tiff_memmap,
110
+ chunks=chunks,
111
+ scale_factors=scale_factors,
112
+ )
113
+ finally:
114
+ logger.removeHandler(log_capture)
115
+ logger.propagate = original_propagate
116
+
117
+ # Check warning behavior: when use_tiff_memmap=True with uncompressed file, no warning should be raised
118
+ log_messages = [record.getMessage() for record in log_capture.buffer]
119
+ has_memmap_warning = any("image data is not memory-mappable" in msg for msg in log_messages)
120
+ if use_tiff_memmap and not compressed:
121
+ assert not has_memmap_warning, (
122
+ "Uncompressed TIFF with memmap=True should not trigger memory-mappable warning"
123
+ )
124
+
125
+ sdata = SpatialData.init_from_elements({"image": im})
126
+ # sanity check: chunks is (c, y, x)
127
+ if scale_factors is None:
128
+ assert isinstance(sdata["image"], DataArray)
129
+ if chunks is not None:
130
+ assert (
131
+ sdata["image"].chunksizes["x"][0] == chunks[2]
132
+ or sdata["image"].chunksizes["x"][0] == sdata["image"].shape[2]
133
+ )
134
+ assert (
135
+ sdata["image"].chunksizes["y"][0] == chunks[1]
136
+ or sdata["image"].chunksizes["y"][0] == sdata["image"].shape[1]
137
+ )
138
+ else:
139
+ assert len(sdata["image"].keys()) == len(scale_factors) + 1
140
+ if chunks is not None:
141
+ assert (
142
+ sdata["image"]["scale0"]["image"].chunksizes["x"][0] == chunks[2]
143
+ or sdata["image"]["scale0"]["image"].chunksizes["x"][0]
144
+ == sdata["image"]["scale0"]["image"].shape[2]
145
+ )
146
+ assert (
147
+ sdata["image"]["scale0"]["image"].chunksizes["y"][0] == chunks[1]
148
+ or sdata["image"]["scale0"]["image"].chunksizes["y"][0]
149
+ == sdata["image"]["scale0"]["image"].shape[1]
150
+ )
151
+
152
+ return sdata
153
+
154
+ def time_io(
155
+ self, scale_factors: list[int] | None, memmap_compressed: tuple[bool, bool], chunks: tuple[int, ...]
156
+ ) -> None:
157
+ """Walltime for data parsing."""
158
+ sdata = self._convert_image(scale_factors, memmap_compressed, chunks)
159
+ sdata.write(self.path_write)
160
+
161
+ def peakmem_io(
162
+ self, scale_factors: list[int] | None, memmap_compressed: tuple[bool, bool], chunks: tuple[int, ...]
163
+ ) -> None:
164
+ """Peak memory for data parsing."""
165
+ sdata = self._convert_image(scale_factors, memmap_compressed, chunks)
166
+ sdata.write(self.path_write)
167
+
168
+
169
+ # if __name__ == "__main__":
170
+ # # Run a single test case for quick verification
171
+ # bench = IOBenchmarkImage()
172
+ #
173
+ # bench.setup()
174
+ # bench.time_io(None, (True, False), (1, 5000, 5000))
175
+ # bench.teardown()
176
+ #
177
+ # # Clean up the shared images temp directory at the end
178
+ # if IOBenchmarkImage._images_temp_dir is not None:
179
+ # IOBenchmarkImage._images_temp_dir.cleanup()
180
+ # IOBenchmarkImage._images_temp_dir = None
@@ -0,0 +1,103 @@
1
+ """ASV benchmarks for spatialdata-io import times.
2
+
3
+ Measures how long it takes to import the package and individual readers
4
+ in a fresh subprocess, isolating import overhead from runtime work.
5
+
6
+ Running (with the current environment, no virtualenv rebuild):
7
+ # Quick sanity check (single iteration):
8
+ asv run --python=same --quick --show-stderr -v -b ImportBenchmark
9
+
10
+ # Full benchmark on current commit:
11
+ asv run --python=same --show-stderr -v -b ImportBenchmark
12
+
13
+ # Compare two branches (using --python=same, one-liner):
14
+ git stash && git checkout main && pip install -e . -q \
15
+ && asv run --python=same -v -b ImportBenchmark \
16
+ && git checkout faster-imports && git stash pop && pip install -e . -q \
17
+ && asv run --python=same -v -b ImportBenchmark
18
+ # Then view the comparison:
19
+ asv compare $(git rev-parse main) $(git rev-parse faster-imports)
20
+
21
+ # Compare two branches (let ASV build virtualenvs, slower first run):
22
+ asv continuous --show-stderr -v -b ImportBenchmark main faster-imports
23
+
24
+ # Generate an HTML report:
25
+ asv publish && asv preview
26
+ """
27
+
28
+ import subprocess
29
+ import sys
30
+
31
+
32
+ def _import_time(statement: str) -> float:
33
+ """Time an import in a fresh subprocess. Returns seconds."""
34
+ code = f"import time; t0=time.perf_counter(); {statement}; print(time.perf_counter()-t0)"
35
+ result = subprocess.run(
36
+ [sys.executable, "-c", code],
37
+ capture_output=True,
38
+ text=True,
39
+ )
40
+ if result.returncode != 0:
41
+ raise RuntimeError(result.stderr)
42
+ return float(result.stdout.strip())
43
+
44
+
45
+ class ImportBenchmark:
46
+ """Import-time benchmarks for spatialdata-io.
47
+
48
+ Each ``time_*`` method is a separate ASV benchmark.
49
+ They run in isolated subprocesses so that one import
50
+ does not warm the cache for the next.
51
+ """
52
+
53
+ # ASV settings tuned for subprocess-based import timing:
54
+ timeout = 120 # seconds before ASV kills a benchmark; generous since each
55
+ # call spawns a subprocess (~2s each × 10 repeats = ~20s worst case)
56
+ repeat = 5 # number of timing samples ASV collects; high because import
57
+ # times have variance from OS caching / disk I/O / background load;
58
+ # ASV reports the median and IQR from these samples
59
+ number = 1 # calls per sample; must be 1 because each call spawns a fresh
60
+ # subprocess — running >1 would just re-import in a warm process
61
+ warmup_time = 0 # seconds of warm-up iterations before timing; disabled because
62
+ # each call is already a cold subprocess — warming up the parent
63
+ # process is meaningless
64
+ processes = 1 # number of ASV worker processes; 1 avoids parallel subprocesses
65
+ # competing for CPU / disk and inflating timings
66
+
67
+ # -- top-level package -------------------------------------------------
68
+
69
+ def time_import_spatialdata_io(self) -> float:
70
+ """Wall time: ``import spatialdata_io`` (lazy, no readers loaded)."""
71
+ return _import_time("import spatialdata_io")
72
+
73
+ # -- single reader via the public API ----------------------------------
74
+
75
+ def time_from_spatialdata_io_import_xenium(self) -> float:
76
+ """Wall time: ``from spatialdata_io import xenium``."""
77
+ return _import_time("from spatialdata_io import xenium")
78
+
79
+ def time_from_spatialdata_io_import_visium(self) -> float:
80
+ """Wall time: ``from spatialdata_io import visium``."""
81
+ return _import_time("from spatialdata_io import visium")
82
+
83
+ def time_from_spatialdata_io_import_visium_hd(self) -> float:
84
+ """Wall time: ``from spatialdata_io import visium_hd``."""
85
+ return _import_time("from spatialdata_io import visium_hd")
86
+
87
+ def time_from_spatialdata_io_import_merscope(self) -> float:
88
+ """Wall time: ``from spatialdata_io import merscope``."""
89
+ return _import_time("from spatialdata_io import merscope")
90
+
91
+ def time_from_spatialdata_io_import_cosmx(self) -> float:
92
+ """Wall time: ``from spatialdata_io import cosmx``."""
93
+ return _import_time("from spatialdata_io import cosmx")
94
+
95
+ # -- key dependencies (reference) --------------------------------------
96
+
97
+ def time_import_spatialdata(self) -> float:
98
+ """Wall time: ``import spatialdata`` (reference)."""
99
+ return _import_time("import spatialdata")
100
+
101
+ def time_import_anndata(self) -> float:
102
+ """Wall time: ``import anndata`` (reference)."""
103
+ return _import_time("import anndata")
@@ -0,0 +1,108 @@
1
+ """Benchmarks for SpatialData IO operations.
2
+
3
+ Configuration:
4
+ Edit SANDBOX_DIR and DATASET below to point to your data.
5
+
6
+ Setup:
7
+ cd <SANDBOX_DIR>/<DATASET>
8
+ python download.py # use the same env where spatialdata is installed
9
+
10
+ Running:
11
+ cd /path/to/spatialdata-io
12
+
13
+ # Quick benchmark (single run, for testing):
14
+ asv run --python=same -b IOBenchmarkXenium --quick --show-stderr -v
15
+
16
+ # Full benchmark (multiple runs, for accurate results):
17
+ asv run --python=same -b IOBenchmarkXenium --show-stderr -v
18
+
19
+ Comparing branches:
20
+ # Run on specific commits:
21
+ asv run main^! -b IOBenchmarkXenium --show-stderr -v
22
+ asv run xenium-labels-dask^! -b IOBenchmarkXenium --show-stderr -v
23
+
24
+ # Or compare two branches directly:
25
+ asv continuous main xenium-labels-dask -b IOBenchmarkXenium --show-stderr -v
26
+
27
+ # View comparison:
28
+ asv compare main xenium-labels-dask
29
+
30
+ Results:
31
+ - Console output shows timing and memory after each run
32
+ - JSON results saved to: .asv/results/
33
+ - Generate HTML report: asv publish && asv preview
34
+ """
35
+
36
+ import inspect
37
+ import shutil
38
+ from pathlib import Path
39
+
40
+ from spatialdata import SpatialData
41
+
42
+ from spatialdata_io import xenium
43
+
44
+ # =============================================================================
45
+ # CONFIGURATION - Edit these paths to match your setup
46
+ # =============================================================================
47
+ SANDBOX_DIR = Path(__file__).parent.parent.parent / "spatialdata-sandbox"
48
+ DATASET = "xenium_2.0.0_io"
49
+ # =============================================================================
50
+
51
+
52
+ def get_paths() -> tuple[Path, Path]:
53
+ """Get paths for benchmark data."""
54
+ path = SANDBOX_DIR / DATASET
55
+ path_read = path / "data"
56
+ path_write = path / "data_benchmark.zarr"
57
+
58
+ if not path_read.exists():
59
+ raise ValueError(f"Data directory not found: {path_read}")
60
+
61
+ return path_read, path_write
62
+
63
+
64
+ class IOBenchmarkXenium:
65
+ timeout = 3600
66
+ repeat = 3
67
+ number = 1
68
+ warmup_time = 0
69
+ processes = 1
70
+
71
+ def setup(self) -> None:
72
+ """Set up paths for benchmarking."""
73
+ self.path_read, self.path_write = get_paths()
74
+ if self.path_write.exists():
75
+ shutil.rmtree(self.path_write)
76
+
77
+ def _read_xenium(self) -> SpatialData:
78
+ """Read xenium data with version-compatible kwargs."""
79
+ signature = inspect.signature(xenium)
80
+ kwargs = {}
81
+ if "cleanup_labels_zarr_tmpdir" in signature.parameters:
82
+ kwargs["cleanup_labels_zarr_tmpdir"] = False
83
+
84
+ return xenium(
85
+ path=str(self.path_read),
86
+ n_jobs=8,
87
+ cell_boundaries=True,
88
+ nucleus_boundaries=True,
89
+ morphology_focus=True,
90
+ cells_as_circles=True,
91
+ **kwargs,
92
+ )
93
+
94
+ def time_io(self) -> None:
95
+ """Walltime for data parsing."""
96
+ sdata = self._read_xenium()
97
+ sdata.write(self.path_write)
98
+
99
+ def peakmem_io(self) -> None:
100
+ """Peak memory for data parsing."""
101
+ sdata = self._read_xenium()
102
+ sdata.write(self.path_write)
103
+
104
+
105
+ if __name__ == "__main__":
106
+ benchmark = IOBenchmarkXenium()
107
+ benchmark.setup()
108
+ benchmark.time_io()
@@ -127,6 +127,7 @@ nitpick_ignore = [
127
127
  # If building the documentation fails because of a missing link that is outside your control,
128
128
  # you can add an exception to this list.
129
129
  ("py:class", "Path"),
130
+ ("py:class", "pathlib._local.Path"),
130
131
  ("py:class", "AnnData"),
131
132
  ("py:class", "SpatialData"),
132
133
  ("py:func", "imageio.imread"), # maybe this can be fixed