From fa290be314a0dc73d9791ca52cd66aa28895e0eb Mon Sep 17 00:00:00 2001 From: Julia Dark Date: Tue, 21 Jan 2025 16:53:17 -0500 Subject: [PATCH 1/4] Use `MultiscaleImage.create` instead of `Group.create` --- apis/python/src/tiledbsoma/_multiscale_image.py | 3 +-- apis/python/src/tiledbsoma/soma_collection.cc | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apis/python/src/tiledbsoma/_multiscale_image.py b/apis/python/src/tiledbsoma/_multiscale_image.py index 27f0c4cd44..85e3cd2c22 100644 --- a/apis/python/src/tiledbsoma/_multiscale_image.py +++ b/apis/python/src/tiledbsoma/_multiscale_image.py @@ -216,9 +216,8 @@ def create( 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, ctx=context.native_context, timestamp=(0, timestamp_ms), ) diff --git a/apis/python/src/tiledbsoma/soma_collection.cc b/apis/python/src/tiledbsoma/soma_collection.cc index 4ab3b5944f..da7b6a0d9b 100644 --- a/apis/python/src/tiledbsoma/soma_collection.cc +++ b/apis/python/src/tiledbsoma/soma_collection.cc @@ -121,6 +121,21 @@ void load_soma_collection(py::module& m) { py::class_( m, "SOMAMultiscaleImage") + .def_static( + "create", + [](std::shared_ptr ctx, + std::string_view uri, + std::optional timestamp) { + try { + SOMAMultiscaleImage::create(uri, ctx, timestamp); + } catch (const std::exception& e) { + TPY_ERROR_LOC(e.what()); + } + }, + py::kw_only(), + "ctx"_a, + "uri"_a, + "timestamp"_a = py::none()) .def_static( "open", &SOMAMultiscaleImage::open, From 9bc74c37aa6efd4e0d7ce261404418ae530285fa Mon Sep 17 00:00:00 2001 From: Julia Dark Date: Wed, 22 Jan 2025 11:04:19 -0500 Subject: [PATCH 2/4] Push down coord space and spatial encoding metadata Write the coordinate space metadata and spatial encoding version metadata for the `MultiscaleImage` in the C++ layer instead of the Python layer. --- .../src/tiledbsoma/_multiscale_image.py | 20 ++++++++---------- apis/python/src/tiledbsoma/soma_collection.cc | 8 ++++++- .../src/soma/soma_multiscale_image.cc | 21 ++++++++++++++++++- .../src/soma/soma_multiscale_image.h | 8 +++++++ .../test/unit_soma_multiscale_image.cc | 6 ++++-- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/apis/python/src/tiledbsoma/_multiscale_image.py b/apis/python/src/tiledbsoma/_multiscale_image.py index 85e3cd2c22..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,22 +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.SOMAMultiscaleImage.create( uri=uri, + 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 da7b6a0d9b..8d5a040bfa 100644 --- a/apis/python/src/tiledbsoma/soma_collection.cc +++ b/apis/python/src/tiledbsoma/soma_collection.cc @@ -125,9 +125,13 @@ void load_soma_collection(py::module& m) { "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, timestamp); + SOMAMultiscaleImage::create( + uri, ctx, coord_space, timestamp); } catch (const std::exception& e) { TPY_ERROR_LOC(e.what()); } @@ -135,6 +139,8 @@ void load_soma_collection(py::module& m) { py::kw_only(), "ctx"_a, "uri"_a, + "axis_names"_a, + "axis_units"_a, "timestamp"_a = py::none()) .def_static( "open", diff --git a/libtiledbsoma/src/soma/soma_multiscale_image.cc b/libtiledbsoma/src/soma/soma_multiscale_image.cc index 225356ba7e..7720ef7a09 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(); } From 46e8e7605df89754af915a0bfe7350342fddd54c Mon Sep 17 00:00:00 2001 From: Julia Dark Date: Thu, 23 Jan 2025 08:47:27 -0500 Subject: [PATCH 3/4] Make coordinate space info const ref --- apis/python/src/tiledbsoma/soma_collection.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/python/src/tiledbsoma/soma_collection.cc b/apis/python/src/tiledbsoma/soma_collection.cc index 8d5a040bfa..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()) { From 65db753ac9d5f3ee4e5c45ee1128b5bd64c2a962 Mon Sep 17 00:00:00 2001 From: Julia Dark <24235303+jp-dark@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:58:06 -0500 Subject: [PATCH 4/4] Update libtiledbsoma/src/soma/soma_multiscale_image.cc Co-authored-by: John Kerl --- libtiledbsoma/src/soma/soma_multiscale_image.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtiledbsoma/src/soma/soma_multiscale_image.cc b/libtiledbsoma/src/soma/soma_multiscale_image.cc index 7720ef7a09..4ad1eb3cff 100644 --- a/libtiledbsoma/src/soma/soma_multiscale_image.cc +++ b/libtiledbsoma/src/soma/soma_multiscale_image.cc @@ -31,7 +31,7 @@ void SOMAMultiscaleImage::create( auto group = SOMAGroup::create( ctx, image_uri.string(), "SOMAMultiscaleImage", timestamp); - // Set spatial encoding metadata. + // Set spatial-encoding metadata. group->set_metadata( SPATIAL_ENCODING_VERSION_KEY, TILEDB_STRING_UTF8,