diff --git a/apis/python/src/tiledbsoma/__init__.py b/apis/python/src/tiledbsoma/__init__.py index 946f9cba40..1410232e7f 100644 --- a/apis/python/src/tiledbsoma/__init__.py +++ b/apis/python/src/tiledbsoma/__init__.py @@ -165,6 +165,7 @@ ) from ._indexer import IntIndexer, tiledbsoma_build_index from ._measurement import Measurement +from ._scene import Scene from ._sparse_nd_array import SparseNDArray from .options import SOMATileDBContext, TileDBCreateOptions from .pytiledbsoma import ( @@ -204,6 +205,7 @@ "SOMA_JOINID", "SOMAError", "SOMATileDBContext", + "Scene", "SparseNDArray", "TileDBCreateOptions", "tiledbsoma_build_index", diff --git a/apis/python/src/tiledbsoma/_experiment.py b/apis/python/src/tiledbsoma/_experiment.py index 07f9ce0346..31a70c8a18 100644 --- a/apis/python/src/tiledbsoma/_experiment.py +++ b/apis/python/src/tiledbsoma/_experiment.py @@ -6,7 +6,7 @@ """Implementation of a SOMA Experiment. """ import functools -from typing import Any, Optional +from typing import Any, Optional, Union from somacore import experiment, query from typing_extensions import Self @@ -15,6 +15,7 @@ from ._dataframe import DataFrame from ._indexer import IntIndexer from ._measurement import Measurement +from ._scene import Scene from ._tdb_handles import Wrapper from ._tiledb_object import AnyTileDBObject @@ -24,6 +25,7 @@ class Experiment( # type: ignore[misc] # __eq__ false positive experiment.Experiment[ # type: ignore[type-var] DataFrame, Collection[Measurement], + Collection[Union[DataFrame, Scene]], AnyTileDBObject, ], ): @@ -43,6 +45,8 @@ class Experiment( # type: ignore[misc] # __eq__ false positive defined in this dataframe. ms (Collection): A collection of named measurements. + spatial (Collection): + A collection of spatial scenes. Example: >>> import tiledbsoma @@ -68,6 +72,7 @@ class Experiment( # type: ignore[misc] # __eq__ false positive _subclass_constrained_soma_types = { "obs": ("SOMADataFrame",), "ms": ("SOMACollection",), + "spatial": ("SOMACollection",), } @classmethod diff --git a/apis/python/src/tiledbsoma/_scene.py b/apis/python/src/tiledbsoma/_scene.py new file mode 100644 index 0000000000..82a07fa217 --- /dev/null +++ b/apis/python/src/tiledbsoma/_scene.py @@ -0,0 +1,39 @@ +# Copyright (c) 2024 TileDB, Inc. +# +# Licensed under the MIT License. + +"""Implementation of a SOMA Scene.""" + + +from typing import Union + +from somacore import scene + +from ._collection import Collection, CollectionBase +from ._dataframe import DataFrame +from ._dense_nd_array import DenseNDArray +from ._sparse_nd_array import SparseNDArray +from ._tiledb_object import AnyTileDBObject + + +class Scene( # type: ignore[misc] # __eq__ false positive + CollectionBase[AnyTileDBObject], + scene.Scene[ # type: ignore[type-var] + Collection[ + Union[DataFrame, DenseNDArray, SparseNDArray] + ], # not just DataFrame and NDArray since NDArray does not have a common `read` + AnyTileDBObject, + ], +): + """TODO: Add documentation for a Scene + + Lifecycle: + Experimental. + """ + + __slots__ = () + + _subclass_constrained_soma_types = { + "exp": ("SOMACollection",), + "ms": ("SOMACollection",), + } diff --git a/apis/python/tests/test_experiment_basic.py b/apis/python/tests/test_experiment_basic.py index 513217afcd..0313fcbbec 100644 --- a/apis/python/tests/test_experiment_basic.py +++ b/apis/python/tests/test_experiment_basic.py @@ -90,6 +90,8 @@ def test_experiment_basic(tmp_path): measurement = ms.add_new_collection("RNA", soma.Measurement) assert soma.Measurement.exists(measurement.uri) assert not soma.Collection.exists(measurement.uri) + spatial = experiment.add_new_collection("spatial", soma.Collection) + assert soma.Collection.exists(spatial.uri) measurement["var"] = create_and_populate_var(urljoin(measurement.uri, "var")) @@ -99,11 +101,13 @@ def test_experiment_basic(tmp_path): x.set("data", nda, use_relative_uri=False) # ---------------------------------------------------------------- - assert len(experiment) == 2 + assert len(experiment) == 3 assert isinstance(experiment.obs, soma.DataFrame) assert isinstance(experiment.ms, soma.Collection) + assert isinstance(experiment.spatial, soma.Collection) assert "obs" in experiment assert "ms" in experiment + assert "spatial" in experiment assert "nonesuch" not in experiment assert experiment.obs == experiment["obs"] diff --git a/libtiledbsoma/src/soma/soma_experiment.cc b/libtiledbsoma/src/soma/soma_experiment.cc index 370c79419b..4aaef17ae3 100644 --- a/libtiledbsoma/src/soma/soma_experiment.cc +++ b/libtiledbsoma/src/soma/soma_experiment.cc @@ -59,12 +59,14 @@ void SOMAExperiment::create( platform_config, timestamp); SOMACollection::create(exp_uri + "/ms", ctx, timestamp); + SOMACollection::create(exp_uri + "/spatial", ctx, timestamp); auto name = std::string(std::filesystem::path(uri).filename()); auto group = SOMAGroup::open( OpenMode::write, exp_uri, ctx, name, timestamp); group->set(exp_uri + "/obs", URIType::absolute, "obs"); group->set(exp_uri + "/ms", URIType::absolute, "ms"); + group->set(exp_uri + "/spatial", URIType::absolute, "spatial"); group->close(); } diff --git a/libtiledbsoma/src/soma/soma_experiment.h b/libtiledbsoma/src/soma/soma_experiment.h index 4c303e0b87..9e26caae26 100644 --- a/libtiledbsoma/src/soma/soma_experiment.h +++ b/libtiledbsoma/src/soma/soma_experiment.h @@ -109,7 +109,10 @@ class SOMAExperiment : public SOMACollection { // A collection of named measurements std::shared_ptr ms_; + + // A collection of spatial scenes + std::shared_ptr spatial_; }; } // namespace tiledbsoma -#endif // SOMA_EXPERIMENT \ No newline at end of file +#endif // SOMA_EXPERIMENT