spatialdata 0.7.3a1__tar.gz → 0.7.3a2__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 (170) hide show
  1. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/workflows/test.yaml +7 -7
  2. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.pre-commit-config.yaml +3 -3
  3. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/PKG-INFO +2 -2
  4. spatialdata-0.7.3a2/_version.py +24 -0
  5. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/pyproject.toml +3 -1
  6. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/_elements.py +3 -22
  7. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/centroids.py +22 -37
  8. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/io_points.py +1 -1
  9. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/io_raster.py +12 -2
  10. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/io_shapes.py +1 -1
  11. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/io_table.py +1 -1
  12. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/datasets.py +4 -1
  13. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/models.py +7 -3
  14. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/conftest.py +95 -22
  15. spatialdata-0.7.3a2/tests/core/operations/conftest.py +1 -0
  16. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_spatialdata_operations.py +14 -13
  17. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_transform.py +1 -1
  18. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_vectorize.py +25 -22
  19. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/query/test_relational_query.py +2 -2
  20. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/query/test_relational_query_match_sdata_to_table.py +9 -8
  21. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/test_data_extent.py +13 -9
  22. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/test_get_attrs.py +5 -9
  23. spatialdata-0.7.3a2/tests/dataloader/conftest.py +1 -0
  24. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_attrs_io.py +2 -0
  25. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_format.py +2 -0
  26. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_metadata.py +5 -0
  27. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_partial_read.py +47 -25
  28. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_pyramids_performance.py +22 -1
  29. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_readwrite.py +43 -15
  30. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/models/test_models.py +6 -5
  31. spatialdata-0.7.3a1/_version.py +0 -34
  32. spatialdata-0.7.3a1/docs/_templates/.gitkeep +0 -0
  33. spatialdata-0.7.3a1/docs/_templates/autosummary/base.rst +0 -5
  34. spatialdata-0.7.3a1/docs/_templates/autosummary/class.rst +0 -61
  35. spatialdata-0.7.3a1/docs/_templates/autosummary/function.rst +0 -5
  36. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.bumpversion.cfg +0 -0
  37. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.editorconfig +0 -0
  38. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  39. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  40. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/codecov.yml +0 -0
  41. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/release.yml +0 -0
  42. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/workflows/build_image.yml +0 -0
  43. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.github/workflows/release.yaml +0 -0
  44. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.gitignore +0 -0
  45. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.gitmodules +0 -0
  46. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.mypy.ini +0 -0
  47. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/.readthedocs.yaml +0 -0
  48. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/CHANGELOG.md +0 -0
  49. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/Dockerfile +0 -0
  50. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/LICENSE +0 -0
  51. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/README.md +0 -0
  52. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/asv.conf.json +0 -0
  53. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/benchmarks/README.md +0 -0
  54. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/benchmarks/__init__.py +0 -0
  55. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/benchmarks/benchmark_imports.py +0 -0
  56. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/benchmarks/spatialdata_benchmark.py +0 -0
  57. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/benchmarks/utils.py +0 -0
  58. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/Makefile +0 -0
  59. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/_static/.gitkeep +0 -0
  60. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/_static/css/custom.css +0 -0
  61. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/_static/img/spatialdata_horizontal.png +0 -0
  62. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/SpatialData.md +0 -0
  63. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/data_formats.md +0 -0
  64. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/dataloader.md +0 -0
  65. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/datasets.md +0 -0
  66. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/io.md +0 -0
  67. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/models.md +0 -0
  68. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/models_utils.md +0 -0
  69. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/operations.md +0 -0
  70. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/testing.md +0 -0
  71. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/transformations.md +0 -0
  72. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api/transformations_utils.md +0 -0
  73. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/api.md +0 -0
  74. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/changelog.md +0 -0
  75. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/conf.py +0 -0
  76. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/contributing.md +0 -0
  77. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/design_doc.md +0 -0
  78. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/extensions/typed_returns.py +0 -0
  79. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/glossary.md +0 -0
  80. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/index.md +0 -0
  81. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/installation.md +0 -0
  82. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/references.bib +0 -0
  83. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/references.md +0 -0
  84. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/docs/user_guide.md +0 -0
  85. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/__init__.py +0 -0
  86. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/__main__.py +0 -0
  87. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_bridges/__init__.py +0 -0
  88. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/__init__.py +0 -0
  89. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/_deepcopy.py +0 -0
  90. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/_utils.py +0 -0
  91. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/concatenate.py +0 -0
  92. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/data_extent.py +0 -0
  93. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/__init__.py +0 -0
  94. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/_utils.py +0 -0
  95. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/aggregate.py +0 -0
  96. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/map.py +0 -0
  97. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/rasterize.py +0 -0
  98. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/rasterize_bins.py +0 -0
  99. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/transform.py +0 -0
  100. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/operations/vectorize.py +0 -0
  101. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/query/__init__.py +0 -0
  102. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/query/_utils.py +0 -0
  103. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/query/relational_query.py +0 -0
  104. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/query/spatial_query.py +0 -0
  105. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/spatialdata.py +0 -0
  106. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_core/validation.py +0 -0
  107. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_docs.py +0 -0
  108. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/__init__.py +0 -0
  109. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/_utils.py +0 -0
  110. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/format.py +0 -0
  111. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_io/io_zarr.py +0 -0
  112. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_logging.py +0 -0
  113. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_types.py +0 -0
  114. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/_utils.py +0 -0
  115. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/config.py +0 -0
  116. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/dataloader/__init__.py +0 -0
  117. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/dataloader/datasets.py +0 -0
  118. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/io/__init__.py +0 -0
  119. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/__init__.py +0 -0
  120. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/_accessor.py +0 -0
  121. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/_utils.py +0 -0
  122. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/chunks_utils.py +0 -0
  123. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/models/pyramids_utils.py +0 -0
  124. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/testing.py +0 -0
  125. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/__init__.py +0 -0
  126. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/_utils.py +0 -0
  127. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/ngff/__init__.py +0 -0
  128. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/ngff/_utils.py +0 -0
  129. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/ngff/ngff_coordinate_system.py +0 -0
  130. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/ngff/ngff_transformations.py +0 -0
  131. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/operations.py +0 -0
  132. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/src/spatialdata/transformations/transformations.py +0 -0
  133. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/__init__.py +0 -0
  134. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/__init__.py +0 -0
  135. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/__init__.py +0 -0
  136. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_aggregations.py +0 -0
  137. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_map.py +0 -0
  138. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_rasterize.py +0 -0
  139. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/operations/test_rasterize_bins.py +0 -0
  140. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/query/__init__.py +0 -0
  141. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/query/test_spatial_query.py +0 -0
  142. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/test_centroids.py +0 -0
  143. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/test_deepcopy.py +0 -0
  144. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/core/test_validation.py +0 -0
  145. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/data/multipolygon.json +0 -0
  146. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/data/points.json +0 -0
  147. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/data/polygon.json +0 -0
  148. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/dataloader/__init__.py +0 -0
  149. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/dataloader/test_datasets.py +0 -0
  150. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/datasets/__init__.py +0 -0
  151. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/datasets/test_datasets.py +0 -0
  152. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/__init__.py +0 -0
  153. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_multi_table.py +0 -0
  154. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/io/test_utils.py +0 -0
  155. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/models/__init__.py +0 -0
  156. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/models/test_accessor.py +0 -0
  157. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/models/test_chunks_utils.py +0 -0
  158. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/models/test_pyramids_utils.py +0 -0
  159. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/test_init.py +0 -0
  160. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/__init__.py +0 -0
  161. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/ngff/__init__.py +0 -0
  162. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/ngff/conftest.py +0 -0
  163. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/ngff/test_ngff_coordinate_system.py +0 -0
  164. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/ngff/test_ngff_transformations.py +0 -0
  165. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/test_transformations.py +0 -0
  166. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/transformations/test_transformations_utils.py +0 -0
  167. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/utils/__init__.py +0 -0
  168. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/utils/test_element_utils.py +0 -0
  169. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/utils/test_sanitize.py +0 -0
  170. {spatialdata-0.7.3a1 → spatialdata-0.7.3a2}/tests/utils/test_testing.py +0 -0
@@ -19,12 +19,12 @@ jobs:
19
19
  fail-fast: false
20
20
  matrix:
21
21
  include:
22
- - {os: windows-latest, python: "3.11", dask-version: "2025.12.0", name: "Dask 2025.12.0"}
23
- - {os: windows-latest, python: "3.13", dask-version: "latest", name: "Dask latest"}
24
- - {os: ubuntu-latest, python: "3.11", dask-version: "latest", name: "Dask latest"}
25
- - {os: ubuntu-latest, python: "3.13", dask-version: "latest", name: "Dask latest"}
26
- - {os: macos-latest, python: "3.11", dask-version: "latest", name: "Dask latest"}
27
- - {os: macos-latest, python: "3.13", prerelease: "allow", name: "Python 3.13 (pre-release)"}
22
+ - {os: windows-latest, python: "3.11", dask-version: "2025.12.0", name: "min dask"}
23
+ - {os: windows-latest, python: "3.14", dask-version: "latest"}
24
+ - {os: ubuntu-latest, python: "3.11", dask-version: "latest"}
25
+ - {os: ubuntu-latest, python: "3.14", dask-version: "latest"}
26
+ - {os: macos-latest, python: "3.11", dask-version: "latest"}
27
+ - {os: macos-latest, python: "3.14", prerelease: "allow", name: "prerelease"}
28
28
  env:
29
29
  OS: ${{ matrix.os }}
30
30
  PYTHON: ${{ matrix.python }}
@@ -59,7 +59,7 @@ jobs:
59
59
  PLATFORM: ${{ matrix.os }}
60
60
  DISPLAY: :42
61
61
  run: |
62
- uv run pytest --cov --color=yes --cov-report=xml
62
+ uv run pytest --cov --color=yes --cov-report=xml -n auto --dist worksteal
63
63
  - name: Upload coverage to Codecov
64
64
  uses: codecov/codecov-action@v5
65
65
  with:
@@ -9,18 +9,18 @@ ci:
9
9
  skip: []
10
10
  repos:
11
11
  - repo: https://github.com/rbubley/mirrors-prettier
12
- rev: v3.8.1
12
+ rev: v3.8.3
13
13
  hooks:
14
14
  - id: prettier
15
15
  exclude: ^.github/workflows/test.yaml
16
16
  - repo: https://github.com/pre-commit/mirrors-mypy
17
- rev: v1.19.1
17
+ rev: v1.20.2
18
18
  hooks:
19
19
  - id: mypy
20
20
  additional_dependencies: [numpy, types-requests]
21
21
  exclude: tests/|docs/
22
22
  - repo: https://github.com/astral-sh/ruff-pre-commit
23
- rev: v0.15.2
23
+ rev: v0.15.12
24
24
  hooks:
25
25
  - id: ruff
26
26
  args: [--fix, --exit-non-zero-on-fix]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdata
3
- Version: 0.7.3a1
3
+ Version: 0.7.3a2
4
4
  Summary: Spatial data format.
5
5
  Project-URL: Documentation, https://spatialdata.scverse.org/en/latest
6
6
  Project-URL: Source, https://github.com/scverse/spatialdata.git
@@ -38,7 +38,7 @@ License: BSD 3-Clause License
38
38
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
39
  License-File: LICENSE
40
40
  Requires-Python: >=3.11
41
- Requires-Dist: anndata>=0.12.10
41
+ Requires-Dist: anndata>=0.9.1
42
42
  Requires-Dist: annsel>=0.1.2
43
43
  Requires-Dist: click
44
44
  Requires-Dist: dask-image
@@ -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.3a2'
22
+ __version_tuple__ = version_tuple = (0, 7, 3, 'a2')
23
+
24
+ __commit_id__ = commit_id = None
@@ -22,7 +22,7 @@ dynamic= [
22
22
  license = {file = "LICENSE"}
23
23
  readme = "README.md"
24
24
  dependencies = [
25
- "anndata>=0.12.10",
25
+ "anndata>=0.9.1",
26
26
  "annsel>=0.1.2",
27
27
  "click",
28
28
  "dask-image",
@@ -69,6 +69,7 @@ test = [
69
69
  "pytest",
70
70
  "pytest-cov",
71
71
  "pytest-mock",
72
+ "pytest-xdist",
72
73
  "torch",
73
74
  ]
74
75
  docs = [
@@ -102,6 +103,7 @@ strict = true
102
103
  addopts = [
103
104
  "--import-mode=importlib", # allow using test files with same name
104
105
  "-s", # print output from tests
106
+ "-p no:napari", # napari registers a pytest plugin via its entry point; disable it here since spatialdata tests don't need it
105
107
  ]
106
108
  # These are all markers coming from xarray, dask or anndata. Added here to silence warnings.
107
109
  markers = [
@@ -21,7 +21,6 @@ from spatialdata.models import (
21
21
  PointsModel,
22
22
  ShapesModel,
23
23
  TableModel,
24
- get_axes_names,
25
24
  get_model,
26
25
  )
27
26
 
@@ -50,6 +49,7 @@ class Elements(UserDict[str, T]):
50
49
  raise KeyError(*e.args) from e
51
50
 
52
51
  def __setitem__(self, key: str, value: T) -> None:
52
+ # note that each __setitem__ in the subclasses calls get_model(), which performs data validation
53
53
  self._add_shared_key(key)
54
54
  super().__setitem__(key, value)
55
55
 
@@ -72,15 +72,7 @@ class Images(Elements[DataArray | DataTree]):
72
72
  schema = get_model(value)
73
73
  if schema not in (Image2DModel, Image3DModel):
74
74
  raise TypeError(f"Unknown element type with schema: {schema!r}.")
75
- ndim = len(get_axes_names(value))
76
- if ndim == 3:
77
- Image2DModel.validate(value)
78
- super().__setitem__(key, value)
79
- elif ndim == 4:
80
- Image3DModel.validate(value)
81
- super().__setitem__(key, value)
82
- else:
83
- NotImplementedError("TODO: implement for ndim > 4.")
75
+ super().__setitem__(key, value)
84
76
 
85
77
 
86
78
  class Labels(Elements[DataArray | DataTree]):
@@ -89,15 +81,7 @@ class Labels(Elements[DataArray | DataTree]):
89
81
  schema = get_model(value)
90
82
  if schema not in (Labels2DModel, Labels3DModel):
91
83
  raise TypeError(f"Unknown element type with schema: {schema!r}.")
92
- ndim = len(get_axes_names(value))
93
- if ndim == 2:
94
- Labels2DModel.validate(value)
95
- super().__setitem__(key, value)
96
- elif ndim == 3:
97
- Labels3DModel.validate(value)
98
- super().__setitem__(key, value)
99
- else:
100
- NotImplementedError("TODO: implement for ndim > 3.")
84
+ super().__setitem__(key, value)
101
85
 
102
86
 
103
87
  class Shapes(Elements[GeoDataFrame]):
@@ -106,7 +90,6 @@ class Shapes(Elements[GeoDataFrame]):
106
90
  schema = get_model(value)
107
91
  if schema != ShapesModel:
108
92
  raise TypeError(f"Unknown element type with schema: {schema!r}.")
109
- ShapesModel.validate(value)
110
93
  super().__setitem__(key, value)
111
94
 
112
95
 
@@ -116,7 +99,6 @@ class Points(Elements[DaskDataFrame]):
116
99
  schema = get_model(value)
117
100
  if schema != PointsModel:
118
101
  raise TypeError(f"Unknown element type with schema: {schema!r}.")
119
- PointsModel.validate(value)
120
102
  super().__setitem__(key, value)
121
103
 
122
104
 
@@ -126,5 +108,4 @@ class Tables(Elements[AnnData]):
126
108
  schema = get_model(value)
127
109
  if schema != TableModel:
128
110
  raise TypeError(f"Unknown element type with schema: {schema!r}.")
129
- TableModel.validate(value)
130
111
  super().__setitem__(key, value)
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import defaultdict
4
3
  from functools import singledispatch
5
4
 
6
- import dask.array as da
5
+ import numpy as np
7
6
  import pandas as pd
8
7
  import xarray as xr
9
8
  from dask.dataframe import DataFrame as DaskDataFrame
@@ -56,40 +55,29 @@ def get_centroids(
56
55
  raise ValueError(f"The object type {type(e)} is not supported.")
57
56
 
58
57
 
59
- def _get_centroids_for_axis(xdata: xr.DataArray, axis: str) -> pd.DataFrame:
58
+ def _get_centroids_for_labels(xdata: xr.DataArray) -> pd.DataFrame:
60
59
  """
61
- Compute the component "axis" of the centroid of each label as a weighted average of the xarray coordinates.
60
+ Compute centroids for all labels in a DataArray in a single O(n_voxels) pass.
62
61
 
63
- Parameters
64
- ----------
65
- xdata
66
- The xarray DataArray containing the labels.
67
- axis
68
- The axis for which the centroids are computed.
69
-
70
- Returns
71
- -------
72
- pd.DataFrame
73
- A DataFrame containing one column, named after "axis", with the centroids of the labels along that axis.
74
- The index of the DataFrame is the collection of label values, sorted in ascending order.
62
+ Works for any number of spatial dimensions (2D and 3D labels).
75
63
  """
76
- centroids: dict[int, float] = defaultdict(float)
77
- for i in xdata[axis]:
78
- portion = xdata.sel(**{axis: i}).data
79
- u = da.unique(portion, return_counts=True)
80
- labels_values = u[0].compute()
81
- counts = u[1].compute()
82
- for j in range(len(labels_values)):
83
- label_value = labels_values[j]
84
- count = counts[j]
85
- centroids[label_value] += count * i.values.item()
86
-
87
- all_labels_values, all_labels_counts = da.unique(xdata.data, return_counts=True)
88
- all_labels = dict(zip(all_labels_values.compute(), all_labels_counts.compute(), strict=True))
89
- for label_value in centroids:
90
- centroids[label_value] /= all_labels[label_value]
91
- centroids = dict(sorted(centroids.items(), key=lambda x: x[0]))
92
- return pd.DataFrame({axis: centroids.values()}, index=list(centroids.keys()))
64
+ arr = xdata.data.compute()
65
+ axes = list(xdata.dims)
66
+
67
+ # Map label values to a contiguous range for bincount efficiency.
68
+ label_ids, inverse = np.unique(arr, return_inverse=True)
69
+ flat_inverse = inverse.ravel()
70
+ counts = np.bincount(flat_inverse) # per-label pixel counts
71
+
72
+ # indexing="ij" (matrix convention) ensures the i-th grid varies along the i-th
73
+ # dimension of the output, correctly aligning with xdata.dims for any number of axes.
74
+ coord_grids = np.meshgrid(*[xdata[ax].values for ax in axes], indexing="ij")
75
+ data: dict[str, np.ndarray] = {}
76
+ for ax, grid in zip(axes, coord_grids, strict=True):
77
+ coord_sums = np.bincount(flat_inverse, weights=grid.ravel().astype(float))
78
+ data[ax] = coord_sums / counts # counts > 0 by construction (unique guarantees this)
79
+
80
+ return pd.DataFrame(data, index=label_ids)
93
81
 
94
82
 
95
83
  @get_centroids.register(DataArray)
@@ -109,10 +97,7 @@ def _(
109
97
  assert len(e["scale0"]) == 1
110
98
  e = next(iter(e["scale0"].values()))
111
99
 
112
- dfs = []
113
- for axis in get_axes_names(e):
114
- dfs.append(_get_centroids_for_axis(e, axis))
115
- df = pd.concat(dfs, axis=1)
100
+ df = _get_centroids_for_labels(e)
116
101
  if not return_background and 0 in df.index:
117
102
  df = df.drop(index=0) # drop the background label
118
103
  t = get_transformation(e, coordinate_system)
@@ -24,7 +24,7 @@ def _read_points(
24
24
  store: str | Path,
25
25
  ) -> DaskDataFrame:
26
26
  """Read points from a zarr store."""
27
- f = zarr.open(store, mode="r")
27
+ f = zarr.open(Path(store), mode="r") # Path avoids zarr v3 URL-parsing special chars (e.g. #) in names
28
28
 
29
29
  version = _parse_version(f, expect_attrs_key=True)
30
30
  assert version is not None
@@ -316,7 +316,7 @@ def _write_raster(
316
316
  **metadata,
317
317
  )
318
318
  elif isinstance(raster_data, DataTree):
319
- _write_raster_datatree(
319
+ group = _write_raster_datatree(
320
320
  raster_type,
321
321
  group,
322
322
  name,
@@ -409,7 +409,7 @@ def _write_raster_datatree(
409
409
  raster_format: RasterFormatType,
410
410
  storage_options: JSONDict | list[JSONDict] | None = None,
411
411
  **metadata: str | JSONDict | list[JSONDict],
412
- ) -> None:
412
+ ) -> zarr.Group:
413
413
  """Write raster data of type DataTree to disk.
414
414
 
415
415
  Parameters
@@ -460,6 +460,15 @@ def _write_raster_datatree(
460
460
  # os.replace is called. These can also be alleviated by using 'single-threaded' scheduler.
461
461
  da.compute(*dask_delayed, optimize_graph=False)
462
462
 
463
+ # Workaround for https://github.com/scverse/spatialdata/issues/1024.
464
+ # ome-zarr-py bundles write_multiscales_metadata() as a dask.delayed task in the compute=False
465
+ # code path (see https://github.com/ome/ome-zarr-py/issues/580). When da.compute() runs with
466
+ # the 'processes' scheduler that task executes in a subprocess: the on-disk zarr.json is written
467
+ # correctly, but the zarr.Group held in this process keeps its original in-memory GroupMetadata
468
+ # and never sees the update. Re-opening the group forces a fresh read from the store.
469
+ # This workaround should not be needed once https://github.com/ome/ome-zarr-py/issues/580 is fixed.
470
+ group = zarr.open_group(store=group.store, path=group.path, mode="r+", use_consolidated=False)
471
+
463
472
  trans_group = group["labels"][element_name] if raster_type == "labels" else group
464
473
  overwrite_coordinate_transformations_raster(
465
474
  group=trans_group,
@@ -467,6 +476,7 @@ def _write_raster_datatree(
467
476
  axes=tuple(input_axes),
468
477
  raster_format=raster_format,
469
478
  )
479
+ return group
470
480
 
471
481
 
472
482
  def write_image(
@@ -34,7 +34,7 @@ def _read_shapes(
34
34
  store: str | Path,
35
35
  ) -> GeoDataFrame:
36
36
  """Read shapes from a zarr store."""
37
- f = zarr.open(store, mode="r")
37
+ f = zarr.open(Path(store), mode="r") # Path avoids zarr v3 URL-parsing special chars (e.g. #) in names
38
38
  version = _parse_version(f, expect_attrs_key=True)
39
39
  assert version is not None
40
40
  shape_format = ShapesFormats[version]
@@ -22,7 +22,7 @@ from spatialdata.models import TableModel, get_table_keys
22
22
  def _read_table(store: str | Path) -> AnnData:
23
23
  table = read_anndata_zarr(str(store))
24
24
 
25
- f = zarr.open(store, mode="r")
25
+ f = zarr.open(Path(store), mode="r") # Path avoids zarr v3 URL-parsing special chars (e.g. #) in names
26
26
  version = _parse_version(f, expect_attrs_key=False)
27
27
  assert version is not None
28
28
  table_format = TablesFormats[version]
@@ -397,7 +397,10 @@ def blobs_annotating_element(name: BlobsTypes) -> SpatialData:
397
397
  index = sdata[name].index
398
398
  instance_id = index.compute().tolist() if isinstance(index, dask.dataframe.Index) else index.tolist()
399
399
  n = len(instance_id)
400
- new_table = AnnData(shape=(n, 0), obs={"region": pd.Categorical([name] * n), "instance_id": instance_id})
400
+ obs_df = pd.DataFrame(
401
+ {"region": pd.Categorical([name] * n), "instance_id": instance_id}, index=[str(i) for i in range(n)]
402
+ )
403
+ new_table = AnnData(shape=(n, 0), obs=obs_df)
401
404
  new_table = TableModel.parse(new_table, region=name, region_key="region", instance_key="instance_id")
402
405
  del sdata.tables["table"]
403
406
  sdata["table"] = new_table
@@ -266,7 +266,7 @@ class RasterSchema:
266
266
  # Chunk single scale images
267
267
  if chunks is not None:
268
268
  if isinstance(chunks, tuple):
269
- chunks = {dim: chunks[index] for index, dim in enumerate(data.dims)}
269
+ chunks = dict(zip(data.dims, chunks, strict=True))
270
270
  data = data.chunk(chunks=chunks)
271
271
  # recompute coordinates for (multiscale) spatial image
272
272
  data = compute_coordinates(data)
@@ -1250,14 +1250,17 @@ Schema_t: TypeAlias = (
1250
1250
 
1251
1251
  def get_model(
1252
1252
  e: SpatialElement,
1253
+ validate: bool = True,
1253
1254
  ) -> Schema_t:
1254
1255
  """
1255
- Get the model for the given element.
1256
+ Get the model for the given element. Validate using the model if `validate` is `True`.
1256
1257
 
1257
1258
  Parameters
1258
1259
  ----------
1259
1260
  e
1260
1261
  The element.
1262
+ validate
1263
+ Whether to validate the element using the model.
1261
1264
 
1262
1265
  Returns
1263
1266
  -------
@@ -1268,7 +1271,8 @@ def get_model(
1268
1271
  schema: Schema_t,
1269
1272
  e: SpatialElement,
1270
1273
  ) -> Schema_t:
1271
- schema.validate(e)
1274
+ if validate:
1275
+ schema.validate(e)
1272
1276
  return schema
1273
1277
 
1274
1278
  if isinstance(e, DataArray | DataTree):
@@ -1,6 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Sequence
3
+ import os
4
+ import sys
5
+
6
+ # Disable numba JIT for the test suite (the test data is small so initializing the JIT is slower than using plain
7
+ # Python). Force-set (not setdefault) so the runner environment cannot accidentally override with "0".
8
+ os.environ["NUMBA_DISABLE_JIT"] = "1"
9
+ # If a pytest plugin already imported numba before this conftest ran, patch the cached config value too.
10
+ if "numba.core.config" in sys.modules:
11
+ sys.modules["numba.core.config"].NUMBA_DISABLE_JIT = 1
12
+
13
+ import copy as _copy
14
+ from collections.abc import Callable, Sequence
4
15
  from pathlib import Path
5
16
  from typing import Any
6
17
 
@@ -33,6 +44,25 @@ from spatialdata.models import (
33
44
  TableModel,
34
45
  )
35
46
 
47
+
48
+ def _fast_deepcopy_sdata(sd: SpatialData) -> SpatialData:
49
+ """
50
+ Fast deepcopy for SpatialData objects in tests.
51
+
52
+ Uses copy.deepcopy (which skips model re-validation) and manually restores
53
+ the attrs that copy.deepcopy loses for DaskDataFrame (issue #503) and
54
+ GeoDataFrame (issue #286).
55
+ """
56
+ points_attrs = {k: _copy.deepcopy(v._attrs) for k, v in sd.points.items()}
57
+ shapes_attrs = {k: _copy.deepcopy(v.attrs) for k, v in sd.shapes.items()}
58
+ sd_copy = _copy.deepcopy(sd)
59
+ for k, attrs in points_attrs.items():
60
+ sd_copy.points[k]._attrs = attrs
61
+ for k, attrs in shapes_attrs.items():
62
+ sd_copy.shapes[k].attrs = attrs
63
+ return sd_copy
64
+
65
+
36
66
  SEED = 0
37
67
  RNG = default_rng(seed=SEED)
38
68
 
@@ -41,16 +71,26 @@ MULTIPOLYGON_PATH = Path(__file__).parent / "data/polygon.json"
41
71
  POINT_PATH = Path(__file__).parent / "data/points.json"
42
72
 
43
73
 
44
- @pytest.fixture()
45
- def images() -> SpatialData:
74
+ @pytest.fixture(scope="session")
75
+ def _images_session() -> SpatialData:
46
76
  return SpatialData(images=_get_images())
47
77
 
48
78
 
49
79
  @pytest.fixture()
50
- def labels() -> SpatialData:
80
+ def images(_images_session: SpatialData) -> SpatialData:
81
+ return _fast_deepcopy_sdata(_images_session)
82
+
83
+
84
+ @pytest.fixture(scope="session")
85
+ def _labels_session() -> SpatialData:
51
86
  return SpatialData(labels=_get_labels())
52
87
 
53
88
 
89
+ @pytest.fixture()
90
+ def labels(_labels_session: SpatialData) -> SpatialData:
91
+ return _fast_deepcopy_sdata(_labels_session)
92
+
93
+
54
94
  @pytest.fixture()
55
95
  def shapes() -> SpatialData:
56
96
  return SpatialData(shapes=_get_shapes())
@@ -87,8 +127,8 @@ def tables() -> list[AnnData]:
87
127
  return _tables
88
128
 
89
129
 
90
- @pytest.fixture()
91
- def full_sdata() -> SpatialData:
130
+ @pytest.fixture(scope="session")
131
+ def _full_sdata_session() -> SpatialData:
92
132
  return SpatialData(
93
133
  images=_get_images(),
94
134
  labels=_get_labels(),
@@ -98,6 +138,22 @@ def full_sdata() -> SpatialData:
98
138
  )
99
139
 
100
140
 
141
+ @pytest.fixture()
142
+ def full_sdata(_full_sdata_session: SpatialData) -> SpatialData:
143
+ return _fast_deepcopy_sdata(_full_sdata_session)
144
+
145
+
146
+ @pytest.fixture(scope="session")
147
+ def _sdata_full_session() -> SpatialData:
148
+ return SpatialData(
149
+ images=_get_images(),
150
+ labels=_get_labels(),
151
+ shapes=_get_shapes(),
152
+ points=_get_points(),
153
+ tables=_get_tables(region="labels2d"),
154
+ )
155
+
156
+
101
157
  @pytest.fixture(
102
158
  # params=["labels"]
103
159
  params=["full", "empty"]
@@ -110,15 +166,9 @@ def full_sdata() -> SpatialData:
110
166
  ]
111
167
  # + ["empty_" + x for x in ["table"]] # TODO: empty table not supported yet
112
168
  )
113
- def sdata(request) -> SpatialData:
169
+ def sdata(request, _sdata_full_session: SpatialData) -> SpatialData:
114
170
  if request.param == "full":
115
- return SpatialData(
116
- images=_get_images(),
117
- labels=_get_labels(),
118
- shapes=_get_shapes(),
119
- points=_get_points(),
120
- tables=_get_tables(region="labels2d"),
121
- )
171
+ return _fast_deepcopy_sdata(_sdata_full_session)
122
172
  if request.param == "empty":
123
173
  return SpatialData()
124
174
  return request.getfixturevalue(request.param)
@@ -304,18 +354,38 @@ def _get_new_table(spatial_element: None | str | Sequence[str], instance_id: Non
304
354
  return TableModel.parse(adata=adata, spatial_element=spatial_element, instance_id=instance_id)
305
355
 
306
356
 
307
- @pytest.fixture()
308
- def labels_blobs() -> ArrayLike:
309
- """Create a 2D labels."""
357
+ @pytest.fixture(scope="session")
358
+ def _labels_blobs_session() -> ArrayLike:
310
359
  return BlobsDataset()._labels_blobs()
311
360
 
312
361
 
313
362
  @pytest.fixture()
314
- def sdata_blobs() -> SpatialData:
363
+ def labels_blobs(_labels_blobs_session: ArrayLike) -> ArrayLike:
315
364
  """Create a 2D labels."""
365
+ return deepcopy(_labels_blobs_session)
366
+
367
+
368
+ @pytest.fixture(scope="session")
369
+ def _sdata_blobs_session() -> SpatialData:
316
370
  from spatialdata.datasets import blobs
317
371
 
318
- return deepcopy(blobs(256, 300, 3))
372
+ return blobs(256, 300, 3)
373
+
374
+
375
+ @pytest.fixture()
376
+ def sdata_blobs(_sdata_blobs_session: SpatialData) -> SpatialData:
377
+ """Create a 2D labels."""
378
+ return _fast_deepcopy_sdata(_sdata_blobs_session)
379
+
380
+
381
+ @pytest.fixture()
382
+ def blobs_factory(_sdata_blobs_session: SpatialData) -> Callable[[], SpatialData]:
383
+ """Return a factory that creates cheap fresh copies of the session-scoped blobs dataset."""
384
+
385
+ def _make() -> SpatialData:
386
+ return _fast_deepcopy_sdata(_sdata_blobs_session)
387
+
388
+ return _make
319
389
 
320
390
 
321
391
  def _make_points(coordinates: np.ndarray) -> DaskDataFrame:
@@ -542,7 +612,8 @@ def complex_sdata() -> SpatialData:
542
612
  "instance_id": range(1, 51), # Skip background (0)
543
613
  "cell_type": pd.Categorical(RNG.choice(["T cell", "B cell", "Macrophage"], size=50)),
544
614
  "size": RNG.uniform(10, 100, size=50),
545
- }
615
+ },
616
+ index=[str(i) for i in range(50)],
546
617
  )
547
618
 
548
619
  var1 = pd.DataFrame(
@@ -577,7 +648,8 @@ def complex_sdata() -> SpatialData:
577
648
  "category": pd.Categorical(RNG.choice(["A", "B", "C"], size=total_items)),
578
649
  "value": RNG.normal(size=total_items),
579
650
  "count": RNG.poisson(10, size=total_items),
580
- }
651
+ },
652
+ index=[str(i) for i in range(total_items)],
581
653
  )
582
654
 
583
655
  var2 = pd.DataFrame(
@@ -608,7 +680,8 @@ def complex_sdata() -> SpatialData:
608
680
  "cluster": pd.Categorical(RNG.choice(["cluster_1", "cluster_2", "cluster_3"], size=40)),
609
681
  "sample": pd.Categorical(["sample_A"] * 20 + ["sample_B"] * 20),
610
682
  "qc_pass": RNG.choice([True, False], p=[0.8, 0.2], size=40),
611
- }
683
+ },
684
+ index=[str(i) for i in range(40)],
612
685
  )
613
686
 
614
687
  var3 = pd.DataFrame(
@@ -0,0 +1 @@
1
+ from __future__ import annotations