diff --git a/apis/python/src/tiledbsoma/_multiscale_image.py b/apis/python/src/tiledbsoma/_multiscale_image.py index 27f0c4cd44..3d09cfc770 100644 --- a/apis/python/src/tiledbsoma/_multiscale_image.py +++ b/apis/python/src/tiledbsoma/_multiscale_image.py @@ -29,7 +29,6 @@ from ._constants import ( SOMA_COORDINATE_SPACE_METADATA_KEY, SOMA_MULTISCALE_IMAGE_SCHEMA, - SOMA_SPATIAL_ENCODING_VERSION, SOMA_SPATIAL_VERSION_METADATA_KEY, SPATIAL_DISCLAIMER, ) @@ -172,8 +171,12 @@ def create( context = _validate_soma_tiledb_context(context) # Create the coordinate space. - if not isinstance(coordinate_space, CoordinateSpace): - coordinate_space = CoordinateSpace.from_axis_names(coordinate_space) + if isinstance(coordinate_space, CoordinateSpace): + axis_names = tuple(axis.name for axis in coordinate_space) + axis_units = tuple(axis.unit for axis in coordinate_space) + else: + axis_names = tuple(coordinate_space) + axis_units = tuple(len(axis_names) * [None]) ndim = len(coordinate_space) if has_channel_axis: @@ -191,9 +194,7 @@ def create( if data_axis_order is None: axis_permutation = tuple(range(ndim - 1, -1, -1)) else: - axis_indices = { - name: index for index, name in enumerate(coordinate_space.axis_names) - } + axis_indices = {name: index for index, name in enumerate(axis_names)} if has_channel_axis: axis_indices["soma_channel"] = len(coordinate_space) if set(data_axis_order) != set(axis_indices.keys()): @@ -213,23 +214,19 @@ def create( ) _image_meta_str = image_meta.to_json() - coord_space_str = coordinate_space_to_json(coordinate_space) try: timestamp_ms = context._open_timestamp_ms(tiledb_timestamp) - clib.SOMAGroup.create( + clib.SOMAMultiscaleImage.create( uri=uri, - soma_type=somacore.MultiscaleImage.soma_type, + axis_names=axis_names, + axis_units=axis_units, ctx=context.native_context, timestamp=(0, timestamp_ms), ) handle = _tdb_handles.MultiscaleImageWrapper.open( uri, "w", context, tiledb_timestamp ) - handle.metadata[SOMA_SPATIAL_VERSION_METADATA_KEY] = ( - SOMA_SPATIAL_ENCODING_VERSION - ) handle.metadata[SOMA_MULTISCALE_IMAGE_SCHEMA] = _image_meta_str - handle.metadata[SOMA_COORDINATE_SPACE_METADATA_KEY] = coord_space_str multiscale = cls( handle, _dont_call_this_use_create_or_open_instead="tiledbsoma-internal-code", diff --git a/apis/python/src/tiledbsoma/soma_collection.cc b/apis/python/src/tiledbsoma/soma_collection.cc index 4ab3b5944f..8e4330fa48 100644 --- a/apis/python/src/tiledbsoma/soma_collection.cc +++ b/apis/python/src/tiledbsoma/soma_collection.cc @@ -80,8 +80,8 @@ void load_soma_collection(py::module& m) { "create", [](std::shared_ptr ctx, std::string_view uri, - std::optional> axis_names, - std::optional>> + const std::optional>& axis_names, + const std::optional>>& axis_units, std::optional timestamp) { if (axis_units.has_value() && !axis_names.has_value()) { @@ -121,6 +121,27 @@ void load_soma_collection(py::module& m) { py::class_( m, "SOMAMultiscaleImage") + .def_static( + "create", + [](std::shared_ptr ctx, + std::string_view uri, + const std::vector& axis_names, + const std::vector>& axis_units, + std::optional timestamp) { + SOMACoordinateSpace coord_space{axis_names, axis_units}; + try { + SOMAMultiscaleImage::create( + uri, ctx, coord_space, timestamp); + } catch (const std::exception& e) { + TPY_ERROR_LOC(e.what()); + } + }, + py::kw_only(), + "ctx"_a, + "uri"_a, + "axis_names"_a, + "axis_units"_a, + "timestamp"_a = py::none()) .def_static( "open", &SOMAMultiscaleImage::open, diff --git a/libtiledbsoma/src/soma/soma_multiscale_image.cc b/libtiledbsoma/src/soma/soma_multiscale_image.cc index 225356ba7e..4ad1eb3cff 100644 --- a/libtiledbsoma/src/soma/soma_multiscale_image.cc +++ b/libtiledbsoma/src/soma/soma_multiscale_image.cc @@ -24,11 +24,30 @@ using namespace tiledb; void SOMAMultiscaleImage::create( std::string_view uri, std::shared_ptr ctx, + const SOMACoordinateSpace& coordinate_space, std::optional timestamp) { try { std::filesystem::path image_uri(uri); - SOMAGroup::create( + auto group = SOMAGroup::create( ctx, image_uri.string(), "SOMAMultiscaleImage", timestamp); + + // Set spatial-encoding metadata. + group->set_metadata( + SPATIAL_ENCODING_VERSION_KEY, + TILEDB_STRING_UTF8, + static_cast(SPATIAL_ENCODING_VERSION_VAL.size()), + SPATIAL_ENCODING_VERSION_VAL.c_str(), + true); + + // Set coordinate space metadata. + const auto coord_space_metadata = coordinate_space.to_string(); + group->set_metadata( + SOMA_COORDINATE_SPACE_KEY, + TILEDB_STRING_UTF8, + static_cast(coord_space_metadata.size()), + coord_space_metadata.c_str(), + true); + } catch (TileDBError& e) { throw TileDBSOMAError(e.what()); } diff --git a/libtiledbsoma/src/soma/soma_multiscale_image.h b/libtiledbsoma/src/soma/soma_multiscale_image.h index f68db8b545..747db3e4b1 100644 --- a/libtiledbsoma/src/soma/soma_multiscale_image.h +++ b/libtiledbsoma/src/soma/soma_multiscale_image.h @@ -17,6 +17,7 @@ #include #include "soma_collection.h" +#include "soma_coordinates.h" namespace tiledbsoma { @@ -37,6 +38,7 @@ class SOMAMultiscaleImage : public SOMACollection { static void create( std::string_view uri, std::shared_ptr ctx, + const SOMACoordinateSpace& coordinate_space, std::optional timestamp = std::nullopt); /** @@ -76,10 +78,16 @@ class SOMAMultiscaleImage : public SOMACollection { SOMAMultiscaleImage(SOMAMultiscaleImage&&) = default; ~SOMAMultiscaleImage() = default; + inline const SOMACoordinateSpace& coordinate_space() const { + return coord_space_; + } + private: //=================================================================== //= private non-static //=================================================================== + + SOMACoordinateSpace coord_space_; }; } // namespace tiledbsoma diff --git a/libtiledbsoma/test/unit_soma_multiscale_image.cc b/libtiledbsoma/test/unit_soma_multiscale_image.cc index eb164c4288..fe4cca03aa 100644 --- a/libtiledbsoma/test/unit_soma_multiscale_image.cc +++ b/libtiledbsoma/test/unit_soma_multiscale_image.cc @@ -12,15 +12,17 @@ */ #include "common.h" -TEST_CASE("SOMAMultiscaleImage: basic") { +TEST_CASE("SOMAMultiscaleImage: basic", "[multiscale_image][spatial]") { auto ctx = std::make_shared(); std::string uri = "mem://unit-test-multiscale-image-basic"; - SOMAMultiscaleImage::create(uri, ctx, std::nullopt); + SOMACoordinateSpace coord_space{}; + SOMAMultiscaleImage::create(uri, ctx, coord_space, std::nullopt); auto soma_image = SOMAMultiscaleImage::open( uri, OpenMode::read, ctx, std::nullopt); REQUIRE(soma_image->uri() == uri); REQUIRE(soma_image->ctx() == ctx); REQUIRE(soma_image->type() == "SOMAMultiscaleImage"); + REQUIRE(soma_image->coordinate_space() == coord_space); soma_image->close(); }