Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed docs/_templates/.gitkeep
Empty file.
5 changes: 0 additions & 5 deletions docs/_templates/autosummary/base.rst

This file was deleted.

61 changes: 0 additions & 61 deletions docs/_templates/autosummary/class.rst

This file was deleted.

5 changes: 0 additions & 5 deletions docs/_templates/autosummary/function.rst

This file was deleted.

2 changes: 1 addition & 1 deletion docs/tutorials/notebooks
Submodule notebooks updated 160 files
5 changes: 4 additions & 1 deletion src/spatialdata/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/spatialdata/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 3 additions & 1 deletion tests/core/operations/test_spatialdata_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/core/query/test_relational_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/io/test_attrs_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions tests/io/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
38 changes: 30 additions & 8 deletions tests/io/test_partial_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand All @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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(
Expand All @@ -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
12 changes: 11 additions & 1 deletion tests/io/test_readwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions tests/models/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading