diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_templates/autosummary/base.rst b/docs/_templates/autosummary/base.rst deleted file mode 100644 index e81ca6450..000000000 --- a/docs/_templates/autosummary/base.rst +++ /dev/null @@ -1,5 +0,0 @@ -:github_url: {{ fullname }} - -{% extends "!autosummary/base.rst" %} - -.. http://www.sphinx-doc.org/en/stable/ext/autosummary.html#customizing-templates diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index e4665dfc7..000000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,61 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. add toctree option to make autodoc generate the pages - -.. autoclass:: {{ objname }} - -{% block attributes %} -{% if attributes %} -Attributes table -~~~~~~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in attributes %} - ~{{ fullname }}.{{ item }} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block methods %} -{% if methods %} -Methods table -~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in methods %} - {%- if item != '__init__' %} - ~{{ fullname }}.{{ item }} - {%- endif -%} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block attributes_documentation %} -{% if attributes %} -Attributes -~~~~~~~~~~~ - -{% for item in attributes %} - -.. autoattribute:: {{ [objname, item] | join(".") }} -{%- endfor %} - -{% endif %} -{% endblock %} - -{% block methods_documentation %} -{% if methods %} -Methods -~~~~~~~ - -{% for item in methods %} -{%- if item != '__init__' %} - -.. automethod:: {{ [objname, item] | join(".") }} -{%- endif -%} -{%- endfor %} - -{% endif %} -{% endblock %} diff --git a/docs/_templates/autosummary/function.rst b/docs/_templates/autosummary/function.rst deleted file mode 100644 index 097114364..000000000 --- a/docs/_templates/autosummary/function.rst +++ /dev/null @@ -1,5 +0,0 @@ -:github_url: {{ fullname }} - -{{ fullname | escape | underline}} - -.. autofunction:: {{ fullname }} diff --git a/docs/tutorials/notebooks b/docs/tutorials/notebooks index 8774b0d92..37e6d233b 160000 --- a/docs/tutorials/notebooks +++ b/docs/tutorials/notebooks @@ -1 +1 @@ -Subproject commit 8774b0d927e1d5ad38aec8f545c7bf0591c77fe7 +Subproject commit 37e6d233b3b39a09be6006a3ef923a11fa8e28b6 diff --git a/src/spatialdata/datasets.py b/src/spatialdata/datasets.py index cb7a7b55b..37f529c72 100644 --- a/src/spatialdata/datasets.py +++ b/src/spatialdata/datasets.py @@ -397,7 +397,10 @@ def blobs_annotating_element(name: BlobsTypes) -> SpatialData: index = sdata[name].index instance_id = index.compute().tolist() if isinstance(index, dask.dataframe.Index) else index.tolist() n = len(instance_id) - new_table = AnnData(shape=(n, 0), obs={"region": pd.Categorical([name] * n), "instance_id": instance_id}) + obs_df = pd.DataFrame( + {"region": pd.Categorical([name] * n), "instance_id": instance_id}, index=[str(i) for i in range(n)] + ) + new_table = AnnData(shape=(n, 0), obs=obs_df) new_table = TableModel.parse(new_table, region=name, region_key="region", instance_key="instance_id") del sdata.tables["table"] sdata["table"] = new_table diff --git a/src/spatialdata/models/models.py b/src/spatialdata/models/models.py index 2bfcf88ce..cad35baf7 100644 --- a/src/spatialdata/models/models.py +++ b/src/spatialdata/models/models.py @@ -266,7 +266,7 @@ def parse( # Chunk single scale images if chunks is not None: if isinstance(chunks, tuple): - chunks = {dim: chunks[index] for index, dim in enumerate(data.dims)} + chunks = dict(zip(data.dims, chunks, strict=True)) data = data.chunk(chunks=chunks) # recompute coordinates for (multiscale) spatial image data = compute_coordinates(data) diff --git a/tests/conftest.py b/tests/conftest.py index c97939129..fcf8518c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -542,7 +542,8 @@ def complex_sdata() -> SpatialData: "instance_id": range(1, 51), # Skip background (0) "cell_type": pd.Categorical(RNG.choice(["T cell", "B cell", "Macrophage"], size=50)), "size": RNG.uniform(10, 100, size=50), - } + }, + index=[str(i) for i in range(50)], ) var1 = pd.DataFrame( @@ -577,7 +578,8 @@ def complex_sdata() -> SpatialData: "category": pd.Categorical(RNG.choice(["A", "B", "C"], size=total_items)), "value": RNG.normal(size=total_items), "count": RNG.poisson(10, size=total_items), - } + }, + index=[str(i) for i in range(total_items)], ) var2 = pd.DataFrame( @@ -608,7 +610,8 @@ def complex_sdata() -> SpatialData: "cluster": pd.Categorical(RNG.choice(["cluster_1", "cluster_2", "cluster_3"], size=40)), "sample": pd.Categorical(["sample_A"] * 20 + ["sample_B"] * 20), "qc_pass": RNG.choice([True, False], p=[0.8, 0.2], size=40), - } + }, + index=[str(i) for i in range(40)], ) var3 = pd.DataFrame( diff --git a/tests/core/operations/test_spatialdata_operations.py b/tests/core/operations/test_spatialdata_operations.py index 68b538e0a..65a62d91d 100644 --- a/tests/core/operations/test_spatialdata_operations.py +++ b/tests/core/operations/test_spatialdata_operations.py @@ -619,7 +619,9 @@ def test_transform_to_data_extent(full_sdata: SpatialData, maintain_positioning: "poly", ] full_sdata = full_sdata.subset(elements) - points = full_sdata["points_0"].compute() + points = ( + full_sdata["points_0"].compute().copy() + ) # .copy() avoids SettingWithCopyWarning when adding column below (pandas CoW) points["z"] = points["x"] points = PointsModel.parse(points) full_sdata["points_0_3d"] = points diff --git a/tests/core/query/test_relational_query.py b/tests/core/query/test_relational_query.py index 63e7a6f19..b883973b2 100644 --- a/tests/core/query/test_relational_query.py +++ b/tests/core/query/test_relational_query.py @@ -403,8 +403,8 @@ def test_match_rows_inner_join_non_matching_element(sdata_query_aggregation): def test_match_rows_inner_join_non_matching_table(sdata_query_aggregation): sdata = sdata_query_aggregation - table = sdata["table"][3:] - original_instance_id = table.obs["instance_id"] + table = sdata["table"][3:].copy() + original_instance_id = table.obs["instance_id"].copy() reversed_instance_id = [6, 7, 8, 3, 4, 5] + list(reversed(range(12))) table.obs["instance_id"] = reversed_instance_id sdata["table"] = table diff --git a/tests/io/test_attrs_io.py b/tests/io/test_attrs_io.py index 1f9895eaa..9830ed4eb 100644 --- a/tests/io/test_attrs_io.py +++ b/tests/io/test_attrs_io.py @@ -17,6 +17,7 @@ FORMAT_V02 = SpatialDataContainerFormats["0.2"] +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", [FORMAT_V01, FORMAT_V02]) class TestAttrsIO: """Test SpatialData.attrs read/write for all container formats.""" @@ -94,6 +95,7 @@ def test_attrs_incremental_write( assert sdata_read3.attrs["initial_key"] == "initial_value" +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") def test_attrs_v1_to_v2() -> None: """Test that attrs are preserved when converting from V01 to V02.""" with tempfile.TemporaryDirectory() as tmpdir: diff --git a/tests/io/test_format.py b/tests/io/test_format.py index 3ef1319c3..f3646ef32 100644 --- a/tests/io/test_format.py +++ b/tests/io/test_format.py @@ -122,6 +122,7 @@ def test_format_raster_v1_v2_v3(self, images, rformat: type[SpatialDataFormatTyp # TODO: add tests for TablesFormatV01 and TablesFormatV02 +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") class TestFormatConversions: """Test format conversions between older formats and newer.""" diff --git a/tests/io/test_partial_read.py b/tests/io/test_partial_read.py index 28460046e..8141902e9 100644 --- a/tests/io/test_partial_read.py +++ b/tests/io/test_partial_read.py @@ -4,9 +4,10 @@ import os import re import tempfile +import warnings from collections.abc import Generator, Iterable -from contextlib import contextmanager -from dataclasses import dataclass +from contextlib import contextmanager, nullcontext +from dataclasses import dataclass, field from json import JSONDecodeError from pathlib import Path from typing import TYPE_CHECKING @@ -69,6 +70,7 @@ class PartialReadTestCase: expected_elements: list[str] expected_exceptions: type[Exception] | tuple[type[Exception] | IOError, ...] warnings_patterns: list[str] + zarr_version: int = field(default=3) @pytest.fixture(scope="session") @@ -103,6 +105,7 @@ def sdata_with_corrupted_elem_types_zgroup(session_tmp_path: Path) -> PartialRea expected_elements=not_corrupted, expected_exceptions=(JSONDecodeError, ZarrUserWarning), warnings_patterns=["labels: JSONDecodeError", "Object at"], + zarr_version=2, ) @@ -173,6 +176,7 @@ def sdata_with_corrupted_zattrs_elements(session_tmp_path: Path) -> PartialReadT expected_elements=not_corrupted, expected_exceptions=(OSError, JSONDecodeError), warnings_patterns=warnings_patterns, + zarr_version=2, ) @@ -216,6 +220,7 @@ def sdata_with_corrupted_image_chunks_zarrv2(session_tmp_path: Path) -> PartialR expected_elements=not_corrupted, expected_exceptions=(ArrayNotFoundError,), warnings_patterns=[rf"images/{corrupted}: ArrayNotFoundError"], + zarr_version=2, ) @@ -264,6 +269,7 @@ def sdata_with_corrupted_parquet_zarrv2(session_tmp_path: Path) -> PartialReadTe expected_elements=not_corrupted, expected_exceptions=ArrowInvalid, warnings_patterns=[rf"points/{corrupted}: ArrowInvalid"], + zarr_version=2, ) @@ -303,6 +309,7 @@ def sdata_with_missing_zattrs_element(session_tmp_path: Path) -> PartialReadTest expected_elements=not_corrupted, expected_exceptions=OSError, warnings_patterns=["OSError: Image location"], + zarr_version=2, ) @@ -349,6 +356,7 @@ def sdata_with_missing_image_chunks_zarrv2( expected_elements=not_corrupted, expected_exceptions=(ArrayNotFoundError,), warnings_patterns=[rf"images/{corrupted}: (ArrayNotFoundError|TypeError)"], + zarr_version=2, ) @@ -372,6 +380,7 @@ def sdata_with_invalid_zattrs_element_violating_spec(session_tmp_path: Path) -> expected_elements=not_corrupted, expected_exceptions=KeyError, warnings_patterns=[rf"images/{corrupted}: KeyError: coordinateTransformations"], + zarr_version=2, ) @@ -424,6 +433,7 @@ def _create_sdata_with_table_region_not_found(session_tmp_path: Path, zarr_versi warnings_patterns=[ rf"The table is annotating '{re.escape(corrupted)}', which is not present in the SpatialData object" ], + zarr_version=zarr_version, ) @@ -460,11 +470,17 @@ def sdata_with_table_region_not_found_zarrv2(session_tmp_path: Path) -> PartialR indirect=True, ) def test_read_zarr_with_error(test_case: PartialReadTestCase): - if test_case.expected_exceptions: - with pytest.raises(test_case.expected_exceptions): + ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() + with ctx: + if test_case.zarr_version < 3: + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + if test_case.expected_exceptions: + with pytest.raises(test_case.expected_exceptions): + read_zarr(test_case.path, on_bad_files="error") + else: read_zarr(test_case.path, on_bad_files="error") - else: - read_zarr(test_case.path, on_bad_files="error") @pytest.mark.parametrize( @@ -490,8 +506,14 @@ def test_read_zarr_with_error(test_case: PartialReadTestCase): indirect=True, ) def test_read_zarr_with_warnings(test_case: PartialReadTestCase): - with pytest_warns_multiple(UserWarning, matches=test_case.warnings_patterns): - actual: SpatialData = read_zarr(test_case.path, on_bad_files="warn") + ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() + with ctx: + if test_case.zarr_version < 3: + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + with pytest_warns_multiple(UserWarning, matches=test_case.warnings_patterns): + actual: SpatialData = read_zarr(test_case.path, on_bad_files="warn") actual_elements = {name for _, name, _ in actual.gen_elements()} assert set(test_case.expected_elements) == actual_elements diff --git a/tests/io/test_readwrite.py b/tests/io/test_readwrite.py index 209a43046..2e8fb0861 100644 --- a/tests/io/test_readwrite.py +++ b/tests/io/test_readwrite.py @@ -55,6 +55,7 @@ SDATA_FORMATS = list(SpatialDataContainerFormats.values()) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) class TestReadWrite: def test_images( @@ -743,6 +744,7 @@ def test_single_scale_image_roundtrip_stays_dataarray(tmp_path: Path) -> None: assert list(image_group.keys()) == ["s0"] +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_self_contained(full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: # data only in-memory, so the SpatialData object and all its elements are self-contained @@ -804,6 +806,7 @@ def test_self_contained(full_sdata: SpatialData, sdata_container_format: Spatial assert all(description[element_name] for element_name in description if element_name != "combined") +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_symmetric_difference_with_zarr_store( full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType @@ -846,6 +849,7 @@ def test_symmetric_difference_with_zarr_store( } +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_change_path_of_subset(full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: """A subset SpatialData object has not Zarr path associated, show that we can reassign the path""" @@ -917,6 +921,7 @@ def test_incremental_io_valid_name(full_sdata: SpatialData) -> None: _check_valid_name(full_sdata.write_transformations) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_incremental_io_attrs(points: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -945,6 +950,7 @@ def test_incremental_io_attrs(points: SpatialData, sdata_container_format: Spati cached_sdata_blobs = blobs() +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("element_name", ["image2d", "labels2d", "points_0", "circles", "table"]) @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_delete_element_from_disk( @@ -996,6 +1002,7 @@ def test_delete_element_from_disk( assert element_path not in on_disk +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("element_name", ["image2d", "labels2d", "points_0", "circles", "table"]) @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_element_already_on_disk_different_type( @@ -1127,6 +1134,7 @@ def test_reading_invalid_name(tmp_path: Path): ) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_write_store_unconsolidated_and_read(full_sdata, sdata_container_format: SpatialDataContainerFormatType): with tempfile.TemporaryDirectory() as tmpdir: @@ -1139,6 +1147,7 @@ def test_write_store_unconsolidated_and_read(full_sdata, sdata_container_format: assert_spatial_data_objects_are_identical(full_sdata, second_read) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_can_read_sdata_with_reconsolidation(full_sdata, sdata_container_format: SpatialDataContainerFormatType): with tempfile.TemporaryDirectory() as tmpdir: @@ -1208,7 +1217,8 @@ def test_sdata_with_nan_in_obs(tmp_path: Path) -> None: "instance": [0, 0], "column_only_region1": ["string", np.nan], "column_only_region2": [np.nan, 3], - } + }, + index=["0", "1"], ) ), region_key="region", diff --git a/tests/models/test_models.py b/tests/models/test_models.py index 22e63cc1a..e9b81942c 100644 --- a/tests/models/test_models.py +++ b/tests/models/test_models.py @@ -333,10 +333,10 @@ def test_points_model( return if coordinates is not None: coordinates = coordinates.copy() - coords = ["A", "B", "C", "x", "y", "z"] n = 10 - data = pd.DataFrame(RNG.integers(0, 101, size=(n, 6)), columns=coords) - data["target"] = pd.Series(RNG.integers(0, 2, size=(n,))).astype(str) + coord_cols = ["A", "B", "C"] if coordinates is not None else ["x", "y", "z"] + data = pd.DataFrame(RNG.integers(0, 101, size=(n, len(coord_cols))), columns=coord_cols) + data["target"] = pd.Categorical(pd.Series(RNG.integers(0, 2, size=(n,))).astype(str)) data["cell_id"] = pd.Series(RNG.integers(0, 5, size=(n,))).astype(np.int_) data["anno"] = pd.Series(RNG.integers(0, 1, size=(n,))).astype(np.int_) # to test for non-contiguous indices