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
21 changes: 13 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
# Changelog

## Version 0.1.0

- Migrating changes from SCE, replace `validate` with `_validate`.

## Version 0.0.11-0.0.13

- BUGFIX: `to_anndata()` only populates `obsm` with spatial coordinates if the original `SpatialExperiment` has spatial coordinates (PR #53, #54)
- Enhance docstring for `to_anndata()` to describe the structure of returned AnnData object (PR #55)


## Version 0.0.10

- Add an affine function that computes a `rasterio.Affine` object given a `scale_factor`. This assumes a simple scaling where the origin is (0,0) in the spatial coordinate system corresponding to the top-left pixel (0,0). More complex alignments would require explicit affine transforms.
- Ensure img_raster() consistently returns a PIL.Image.Image.
- Add to_numpy() method.
- Changes to how caching works in remote images.


## Version 0.0.9
- Added `to_anndata()` in main `SpatialExperiment` class (PR #50)

- Added `to_anndata()` in main `SpatialExperiment` class (PR #50)

## Version 0.0.8
- Set the expected column names for image data slot (PR #46)

- 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)
- Refactored `get_img_idx` for improved maintainability
- Disambiguated `get_img_data` between `_imgutils.py` and `SpatialExperiment.py`
- Moved `SpatialFeatureExperiment` into its own package


## Version 0.0.6

- Added `read_tenx_visium()` function to load 10x Visium data as SpatialExperiment
- Added `combine_columns` function
- Implemented `__eq__` override for `SpatialImage` subclasses


## Version 0.0.5
- Implementing a placeholder `SpatialFeatureExperiment` class. This version only implements the data structure to hold various geometries but none of the methods except for slicing.

- Implementing a placeholder `SpatialFeatureExperiment` class. This version only implements the data structure to hold various geometries but none of the methods except for slicing.

## Version 0.0.3 - 0.0.4
- Streamlining the `SpatialImage` class implementations.

- Streamlining the `SpatialImage` class implementations.

## Version 0.0.1 - 0.0.2

- Initial version of the SpatialExperiment class with the additional slots.
- Allow spatial coordinates to be a numpy array
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ python_requires = >=3.9
# For more information, check out https://semver.org/.
install_requires =
importlib-metadata; python_version<"3.8"
biocframe>=0.6.3
biocutils>=0.2
singlecellexperiment>=0.5.7
biocframe>=0.7.2
biocutils>=0.3.3
singlecellexperiment>=0.6.0
pillow>=11.0
requests
scipy~=1.13
Expand Down
46 changes: 25 additions & 21 deletions src/spatialexperiment/spatialexperiment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence, Union, Tuple
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
from urllib.parse import urlparse
from warnings import warn

Expand Down Expand Up @@ -30,10 +32,10 @@
_validate_spatial_coords_names,
)
from .spatialimage import (
VirtualSpatialImage,
StoredSpatialImage,
RemoteSpatialImage,
LoadedSpatialImage,
RemoteSpatialImage,
StoredSpatialImage,
VirtualSpatialImage,
construct_spatial_image_class,
)

Expand All @@ -60,7 +62,7 @@ def __init__(
column_data: Optional[BiocFrame] = None,
row_names: Optional[List[str]] = None,
column_names: Optional[List[str]] = None,
metadata: Optional[dict] = None,
metadata: Optional[Union[Dict[str, Any], ut.NamedList]] = None,
reduced_dims: Optional[Dict[str, Any]] = None,
main_experiment_name: Optional[str] = None,
alternative_experiments: Optional[Dict[str, Any]] = None,
Expand All @@ -69,7 +71,7 @@ def __init__(
column_pairs: Optional[Any] = None,
spatial_coords: Optional[Union[BiocFrame, np.ndarray]] = None,
img_data: Optional[BiocFrame] = None,
validate: bool = True,
_validate: bool = True,
**kwargs,
) -> None:
"""Initialize a spatial experiment.
Expand Down Expand Up @@ -179,7 +181,7 @@ def __init__(
Image data are coerced to a
:py:class:`~biocframe.BiocFrame.BiocFrame`. Defaults to None.

validate:
_validate:
Internal use only.
"""
super().__init__(
Expand All @@ -196,7 +198,7 @@ def __init__(
row_pairs=row_pairs,
column_pairs=column_pairs,
alternative_experiment_check_dim_names=alternative_experiment_check_dim_names,
validate=validate,
_validate=_validate,
**kwargs,
)

Expand All @@ -217,7 +219,7 @@ def __init__(
self._cols = column_data
self._spatial_coords = spatial_coords

if validate:
if _validate:
_validate_column_data(column_data=column_data)
_validate_img_data(img_data=img_data)
_validate_sample_ids(column_data=column_data, img_data=img_data)
Expand Down Expand Up @@ -265,6 +267,7 @@ def __deepcopy__(self, memo=None, _nil=[]):
column_pairs=_col_pair_copy,
spatial_coords=_spatial_coords_copy,
img_data=_img_data_copy,
_validate=False,
)

def __copy__(self):
Expand All @@ -288,6 +291,7 @@ def __copy__(self):
column_pairs=self._column_pairs,
spatial_coords=self._spatial_coords,
img_data=self._img_data,
_validate=False,
)

def copy(self):
Expand Down Expand Up @@ -399,7 +403,7 @@ def set_spatial_coordinates(
self,
spatial_coords: Optional[Union[BiocFrame, np.ndarray]],
in_place: bool = False,
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Set new spatial coordinates.

Args:
Expand Down Expand Up @@ -434,7 +438,7 @@ def set_spatial_coords(
self,
spatial_coords: Optional[Union[BiocFrame, np.ndarray]],
in_place: bool = False,
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Alias for :py:meth:`~set_spatial_coordinates`."""
return self.set_spatial_coordinates(spatial_coords=spatial_coords, in_place=in_place)

Expand Down Expand Up @@ -487,7 +491,7 @@ def get_spatial_coords_names(self) -> List[str]:

def set_spatial_coordinates_names(
self, spatial_coords_names: List[str], in_place: bool = False
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Set new spatial coordinates names.

Args:
Expand All @@ -514,7 +518,7 @@ def set_spatial_coordinates_names(
output._spatial_coords = new_spatial_coords
return output

def set_spatial_coords_names(self, spatial_coords_names: List[str], in_place: bool = False) -> "SpatialExperiment":
def set_spatial_coords_names(self, spatial_coords_names: List[str], in_place: bool = False) -> SpatialExperiment:
"""Alias for :py:meth:`~set_spatial_coordinates_names`."""
return self.set_spatial_coordinates_names(spatial_coords_names=spatial_coords_names, in_place=in_place)

Expand Down Expand Up @@ -562,7 +566,7 @@ def get_img_data(self) -> BiocFrame:
"""Alias for :py:meth:`~get_image_data`."""
return self.get_image_data()

def set_image_data(self, img_data: Optional[BiocFrame], in_place: bool = False) -> "SpatialExperiment":
def set_image_data(self, img_data: Optional[BiocFrame], in_place: bool = False) -> SpatialExperiment:
"""Set new image data.

Args:
Expand Down Expand Up @@ -591,7 +595,7 @@ def set_image_data(self, img_data: Optional[BiocFrame], in_place: bool = False)
output._img_data = img_data
return output

def set_img_data(self, img_data: BiocFrame, in_place: bool = False) -> "SpatialExperiment":
def set_img_data(self, img_data: BiocFrame, in_place: bool = False) -> SpatialExperiment:
"""Alias for :py:meth:`~set_image_data`."""
return self.set_image_data(img_data=img_data, in_place=in_place)

Expand Down Expand Up @@ -666,7 +670,7 @@ def set_column_data(
cols: Optional[BiocFrame],
replace_column_names: bool = False,
in_place: bool = False,
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Override: Set sample data.

Args:
Expand Down Expand Up @@ -710,7 +714,7 @@ def get_slice(
self,
rows: Optional[Union[str, int, bool, Sequence]],
columns: Optional[Union[str, int, bool, Sequence]],
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Alias for :py:attr:`~__getitem__`."""

spe = super().get_slice(rows=rows, columns=columns)
Expand Down Expand Up @@ -820,7 +824,7 @@ def add_img(
image_id: Union[str, bool, None],
load: bool = True,
in_place: bool = False,
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Add a new image entry.

Args:
Expand Down Expand Up @@ -883,7 +887,7 @@ def add_img(

def remove_img(
self, sample_id: Union[str, bool, None] = None, image_id: Union[str, bool, None] = None, in_place: bool = False
) -> "SpatialExperiment":
) -> SpatialExperiment:
"""Remove an image entry.

Args:
Expand Down Expand Up @@ -1083,11 +1087,11 @@ def to_anndata(
#######>> combine ops <<########
################################

def relaxed_combine_columns(self, *other) -> "SpatialExperiment":
def relaxed_combine_columns(self, *other) -> SpatialExperiment:
"""Wrapper around :py:func:`~relaxed_combine_columns`."""
return relaxed_combine_columns(self, *other)

def combine_columns(self, *other) -> "SpatialExperiment":
def combine_columns(self, *other) -> SpatialExperiment:
"""Wrapper around :py:func:`~combine_columns`."""
return combine_columns(self, *other)

Expand Down
54 changes: 3 additions & 51 deletions src/spatialexperiment/spatialimage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import shutil
import tempfile
from abc import ABC, abstractmethod
from abc import abstractmethod
from functools import lru_cache
from pathlib import Path
from typing import Optional, Tuple, Union
Expand All @@ -19,11 +19,11 @@


# Keeping the same names as the R classes
class VirtualSpatialImage(ABC):
class VirtualSpatialImage(ut.BiocObject):
"""Base class for spatial images."""

def __init__(self, metadata: Optional[dict] = None):
self._metadata = metadata if metadata is not None else {}
super().__init__(metadata=metadata)

#########################
######>> Equality <<#####
Expand All @@ -40,54 +40,6 @@ def __hash__(self):
# Generally, these classes are mutable and shouldn't be used as dict keys or in sets.
return hash(frozenset(self._metadata.items()))

###########################
######>> metadata <<#######
###########################

def get_metadata(self) -> dict:
"""
Returns:
Dictionary of metadata for this object.
"""
return self._metadata

def set_metadata(self, metadata: dict, in_place: bool = False) -> "VirtualSpatialImage":
"""Set additional metadata.

Args:
metadata:
New metadata for this object.

in_place:
Whether to modify the ``VirtualSpatialImage`` in place.

Returns:
A modified ``VirtualSpatialImage`` object, either as a copy of the original
or as a reference to the (in-place-modified) original.
"""
if not isinstance(metadata, dict):
raise TypeError(f"`metadata` must be a dictionary, provided {type(metadata)}.")
output = self._define_output(in_place)
output._metadata = metadata
return output

@property
def metadata(self) -> dict:
"""Alias for :py:attr:`~get_metadata`."""
return self.get_metadata()

@metadata.setter
def metadata(self, metadata: dict):
"""Alias for :py:attr:`~set_metadata` with ``in_place = True``.

As this mutates the original object, a warning is raised.
"""
warn(
"Setting property 'metadata' is an in-place operation, use 'set_metadata' instead",
UserWarning,
)
self.set_metadata(metadata, in_place=True)

##################################
######>> Spatial Props <<#########
##################################
Expand Down
Loading