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.
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/prepare_test_data.yaml +10 -1
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.gitignore +10 -2
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.pre-commit-config.yaml +3 -3
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/PKG-INFO +8 -5
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/README.md +1 -1
- spatialdata_io-0.7.0/_version.py +24 -0
- spatialdata_io-0.7.0/asv.conf.json +23 -0
- spatialdata_io-0.7.0/benchmarks/__init__.py +1 -0
- spatialdata_io-0.7.0/benchmarks/benchmark_image.py +180 -0
- spatialdata_io-0.7.0/benchmarks/benchmark_imports.py +103 -0
- spatialdata_io-0.7.0/benchmarks/benchmark_xenium.py +108 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/conf.py +1 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/pyproject.toml +28 -22
- spatialdata_io-0.7.0/src/spatialdata_io/__init__.py +100 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/__main__.py +342 -47
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/_constants.py +1 -3
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/legacy_anndata.py +68 -55
- spatialdata_io-0.7.0/src/spatialdata_io/readers/_utils/_image.py +151 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/_utils/_utils.py +19 -6
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/codex.py +4 -3
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/cosmx.py +7 -5
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/curio.py +3 -1
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/dbit.py +2 -1
- spatialdata_io-0.7.0/src/spatialdata_io/readers/generic.py +237 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/iss.py +3 -1
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/macsima.py +375 -44
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/mcmicro.py +3 -1
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/merscope.py +3 -1
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/seqfish.py +112 -50
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/steinbock.py +27 -2
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/stereoseq.py +2 -2
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/visium.py +3 -2
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/visium_hd.py +61 -24
- spatialdata_io-0.7.0/src/spatialdata_io/readers/xenium.py +1128 -0
- spatialdata_io-0.7.0/tests/readers/test_utils_image.py +64 -0
- spatialdata_io-0.7.0/tests/test_cli_alignment.py +116 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_generic.py +69 -0
- spatialdata_io-0.7.0/tests/test_init.py +23 -0
- spatialdata_io-0.7.0/tests/test_macsima.py +680 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_seqfish.py +3 -2
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/test_xenium.py +77 -5
- spatialdata_io-0.5.1/_version.py +0 -34
- spatialdata_io-0.5.1/src/spatialdata_io/__init__.py +0 -61
- spatialdata_io-0.5.1/src/spatialdata_io/readers/_utils/_read_10x_h5.py +0 -144
- spatialdata_io-0.5.1/src/spatialdata_io/readers/generic.py +0 -83
- spatialdata_io-0.5.1/src/spatialdata_io/readers/xenium.py +0 -850
- spatialdata_io-0.5.1/tests/test_basic.py +0 -5
- spatialdata_io-0.5.1/tests/test_macsima.py +0 -231
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.bumpversion.cfg +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.codecov.yaml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.editorconfig +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/codecov.yml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/release.yml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/release.yaml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.github/workflows/test.yaml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.mypy.ini +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/.readthedocs.yaml +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/LICENSE +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/Makefile +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_static/.gitkeep +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_templates/.gitkeep +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/_templates/autosummary/class.rst +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/api.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/changelog.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/cli.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/contributing.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/extensions/.gitkeep +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/extensions/typed_returns.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/index.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/make.bat +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/references.bib +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/docs/references.md +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_constants/_enum.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_docs.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/_utils.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/converters/generic_to_zarr.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/experimental/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/src/spatialdata_io/readers/_utils/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/_utils.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/conftest.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/converters/__init__.py +0 -0
- {spatialdata_io-0.5.1 → spatialdata_io-0.7.0}/tests/converters/test_legacy_anndata.py +0 -0
- {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
|
-
#
|
|
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.
|
|
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
|
+
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|

|
|
@@ -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
|