diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b1d68..fd84ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Version 0.0.9 +- Added `to_anndata()` in main `SpatialExperiment` class (PR #50) + +## Version 0.0.8 +- Set the expected column names for image data slot (PR #46) + ## Version 0.0.7 - Added `img_source` function in main SpatialExperiment class and child classes of VirtualSpatialExperiment (PR #36) - Added `remove_img` function (PR #34) diff --git a/setup.cfg b/setup.cfg index e0e090f..8fbcedc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,7 +69,8 @@ exclude = # PDF = ReportLab; RXP optional = pandas - requests + anndata + delayedarray # Add here test requirements (semicolon/line-separated) testing = diff --git a/src/spatialexperiment/__init__.py b/src/spatialexperiment/__init__.py index de0c505..8fd41e7 100644 --- a/src/spatialexperiment/__init__.py +++ b/src/spatialexperiment/__init__.py @@ -26,7 +26,6 @@ ) __all__ = [ - "ProxySpatialFeatureExperiment", "read_tenx_visium", "SpatialExperiment", "LoadedSpatialImage", diff --git a/src/spatialexperiment/spatialexperiment.py b/src/spatialexperiment/spatialexperiment.py index 31f2cf0..b94b942 100644 --- a/src/spatialexperiment/spatialexperiment.py +++ b/src/spatialexperiment/spatialexperiment.py @@ -29,7 +29,13 @@ _validate_spatial_coords, _validate_spatial_coords_names, ) -from .spatialimage import VirtualSpatialImage, construct_spatial_image_class +from .spatialimage import ( + VirtualSpatialImage, + StoredSpatialImage, + RemoteSpatialImage, + LoadedSpatialImage, + construct_spatial_image_class, +) __author__ = "keviny2" __copyright__ = "keviny2" @@ -1009,6 +1015,46 @@ def mirror_img(self, sample_id=None, image_id=None, axis=("h", "v")): def to_spatial_experiment(): raise NotImplementedError() + ################################ + ######>> AnnData interop <<##### + ################################ + + def to_anndata(self, include_alternative_experiments: bool = False) -> "anndata.AnnData": + """Transform :py:class:`~SpatialExperiment`-like into a :py:class:`~anndata.AnnData` representation. + + Args: + include_alternative_experiments: + Whether to transform alternative experiments. + + Returns: + An ``AnnData`` representation of the experiment. + """ + obj, alt_exps = super().to_anndata(include_alternative_experiments=include_alternative_experiments) + + if "spatial" in obj.uns: + raise ValueError("'spatial' key already exists in the metadata. Rename to something else.") + + obj.uns["spatial"] = {} + for _, row in self.img_data: + library_id = row["sample_id"] + "-" + row["image_id"] + obj.uns["spatial"][library_id] = {} + + spi = row["data"] + if isinstance(spi, LoadedSpatialImage): + img = spi.image + elif isinstance(spi, (StoredSpatialImage, RemoteSpatialImage)): + img = spi.img_source() + + obj.uns["spatial"][library_id]["images"] = {"hires": img} # default to `hires` for now + + obj.uns["spatial"][library_id]["scalefactors"] = { + "tissue_hires_scalef": row["scale_factor"] + } # default to `tissue_hires_scalef` for now + + obj.obsm["spatial"] = np.column_stack([self.spatial_coordinates[axis] for axis in self.spatial_coords_names]) + + return obj, alt_exps + ################################ #######>> combine ops <<######## ################################ diff --git a/src/spatialexperiment/spatialimage.py b/src/spatialexperiment/spatialimage.py index 852af7d..1e1900c 100644 --- a/src/spatialexperiment/spatialimage.py +++ b/src/spatialexperiment/spatialimage.py @@ -257,8 +257,8 @@ def __str__(self) -> str: ######>> img props <<####### ############################ - def get_image(self) -> Image.Image: - """Get the image as a PIL Image object.""" + def get_image(self) -> Union[Image.Image, np.ndarray]: + """Get the image as a PIL Image object or ndarray.""" return self._image diff --git a/tests/test_to_anndata.py b/tests/test_to_anndata.py new file mode 100644 index 0000000..258f6a5 --- /dev/null +++ b/tests/test_to_anndata.py @@ -0,0 +1,37 @@ +from copy import deepcopy +from pathlib import Path +import pytest + +__author__ = "keviny2" +__copyright__ = "keviny2" +__license__ = "MIT" + + +def test_to_anndata(spe): + obj, alt_exps = spe.to_anndata() + + assert obj.shape == (500, 200) + + # check that uns has the correct components + assert 'spatial' in obj.uns + assert len(obj.uns['spatial']) == 3 + + library_id = list(obj.uns['spatial'])[0] + assert 'images' in obj.uns['spatial'][library_id] + assert 'scalefactors' in obj.uns['spatial'][library_id] + + assert 'hires' in obj.uns['spatial'][library_id]['images'] + assert isinstance(obj.uns['spatial'][library_id]['images']['hires'], Path) + + # check that obsm has the correct components + assert 'spatial' in obj.obsm + assert obj.obsm['spatial'].shape == (500, 2) + + +def test_to_anndata_spatial_key_exists(spe): + tspe = deepcopy(spe) + + tspe.metadata['spatial'] = "123" + + with pytest.raises(ValueError): + tspe.to_anndata()