From da88b96efaa885064175058b1fbff673d244ae87 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:00:02 -0400 Subject: [PATCH 1/7] Try again --- pyproject.toml | 1 - tests/test_wrappers.py | 7 +- vitessce/file_def_utils.py | 130 ++++++++++++++++++++++++++++ vitessce/utils.py | 116 ------------------------- vitessce/wrappers.py | 171 +++++++++++++++++++++---------------- 5 files changed, 229 insertions(+), 196 deletions(-) create mode 100644 vitessce/file_def_utils.py diff --git a/pyproject.toml b/pyproject.toml index 53ee7f1..9f918d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'Topic :: Multimedia :: Graphics', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 8571b53..f2b525e 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -233,7 +233,6 @@ def test_anndata_with_base_dir(self): file_def_creator = w.make_file_def_creator('A', 0) file_def = file_def_creator('http://localhost:8000') - print(file_def) self.assertEqual(file_def, {'fileType': 'anndata.zarr', 'url': 'http://localhost:8000/test.h5ad.zarr', 'options': { 'obsEmbedding': [{'path': 'obsm/X_umap', 'dims': [0, 1], 'embeddingType': 'UMAP'}], @@ -249,7 +248,6 @@ def test_anndata_with_base_dir_no_names(self): file_def_creator = w.make_file_def_creator('A', 0) file_def = file_def_creator('http://localhost:8000') - print(file_def) self.assertEqual(file_def, {'fileType': 'anndata.zarr', 'url': 'http://localhost:8000/test.h5ad.zarr', 'options': { 'obsEmbedding': [{'path': 'obsm/X_umap', 'dims': [0, 1], 'embeddingType': 'X_umap'}], @@ -396,13 +394,12 @@ def test_multivec_zarr_with_base_dir(self): def test_spatial_data_with_base_dir(self): spatial_data_path = 'test.spatialdata.zarr' - w = SpatialDataWrapper(spatialdata_path=spatial_data_path, image_elem="picture", obs_set_paths=['obs/CellType'], obs_set_names=['Cell Type'], obs_embedding_paths=[ + w = SpatialDataWrapper(sdata_path=spatial_data_path, image_path="images/picture", obs_set_paths=['obs/CellType'], obs_set_names=['Cell Type'], obs_embedding_paths=[ 'obsm/X_umap'], obs_embedding_names=['UMAP']) w.base_dir = data_path w.local_dir_uid = 'spatialdata.zarr' file_def_creator = w.make_file_def_creator('A', 0) file_def = file_def_creator('http://localhost:8000') - print(file_def) self.assertEqual(file_def, - {'fileType': 'spatialdata.zarr', 'url': 'http://localhost:8000/test.spatialdata.zarr', 'options': {'obsSets': {'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}]}, 'image': {'path': 'picture'}}}) + {'fileType': 'spatialdata.zarr', 'url': 'http://localhost:8000/test.spatialdata.zarr', 'options': {'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}], 'image': {'path': 'images/picture'}}}) diff --git a/vitessce/file_def_utils.py b/vitessce/file_def_utils.py new file mode 100644 index 0000000..7484acd --- /dev/null +++ b/vitessce/file_def_utils.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +from functools import partial +from typing import Optional + +import numpy as np + + +def gen_obs_embedding_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None, dims: Optional[list[list[int]]] = None): + if paths is not None: + if "obsEmbedding" not in options: + options["obsEmbedding"] = [] + if names is not None: + for key, mapping in zip(paths, names): + options["obsEmbedding"].append({ + "path": key, + "dims": [0, 1], + "embeddingType": mapping + }) + else: + for mapping in paths: + mapping_key = mapping.split('/')[-1] + options["obsEmbedding"].append({ + "path": mapping, + "dims": [0, 1], + "embeddingType": mapping_key + }) + if dims is not None: + if "obsEmbedding" not in options: + options["obsEmbedding"] = [] + for dim_i, dim in enumerate(dims): + options["obsEmbedding"][dim_i]['dims'] = dim + return options + + +def gen_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None): + if paths is not None: + options["obsSets"] = [] + if names is not None: + names = names + else: + names = [] + for obs in paths: + obs_end_path = obs.split('/')[-1] + names += [obs_end_path] + for obs, name in zip(paths, names): + options["obsSets"].append({ + "name": name, + "path": obs + }) + return options + + +def gen_obs_feature_matrix_schema(options: dict, matrix_path: Optional[str] = None, var_filter_path: Optional[str] = None, init_var_filter_path: Optional[str] = None): + if matrix_path is not None: + options["obsFeatureMatrix"] = { + "path": matrix_path + } + if var_filter_path is not None: + options["obsFeatureMatrix"]["featureFilterPath"] = var_filter_path + if init_var_filter_path is not None: + options["obsFeatureMatrix"]["initialFeatureFilterPath"] = init_var_filter_path + return options + + +def gen_obs_labels_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None): + if paths is not None: + if names is not None and len(paths) == len(names): + # A name was provided for each path element, so use those values. + names = names + else: + # Names were not provided for each path element, + # so fall back to using the final part of each path for the names. + names = [labels_path.split('/')[-1] for labels_path in paths] + obs_labels = [] + for path, name in zip(paths, names): + obs_labels.append({"path": path, "obsLabelsType": name}) + options["obsLabels"] = obs_labels + return options + + +def gen_path_schema(key: str, path: Optional[str], options: dict): + if path is not None: + options[key] = { + "path": path + } + return options + + +gen_obs_locations_schema = partial(gen_path_schema, "obsLocations") +gen_obs_segmentations_schema = partial(gen_path_schema, "obsSegmentations") +gen_obs_spots_schema = partial(gen_path_schema, "obsSpots") +gen_obs_points_schema = partial(gen_path_schema, "obsPoints") +gen_feature_labels_schema = partial(gen_path_schema, "featureLabels") + +def gen_sdata_image_schema(options, path: str, coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None) -> dict: + if path is not None: + options["image"] = { + "path": path + } + if affine_transformation is not None: + options["image"]['coordinateTransformations'] = affine_transformation + if coordinate_system is not None: + options["image"]['coordinateSystem'] = coordinate_system + return options + +def gen_sdata_labels_schema(options, path: str, table_path: str = "tables/table", coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None) -> dict: + if path is not None: + options["labels"] = { + "path": path + } + if table_path is not None: + options["labels"]['tablePath'] = table_path + if affine_transformation is not None: + options["labels"]['coordinateTransformations'] = affine_transformation + if coordinate_system is not None: + options["labels"]['coordinateSystem'] = coordinate_system + return options + + +def gen_sdata_obs_spots_schema(options: dict, shapes_path: Optional[str] = None, table_path: str = "tables/table", region: Optional[str] = None, coordinate_system: Optional[str] = None) -> dict: + if shapes_path is not None: + options['obsSpots'] = { + "path": shapes_path, + "tablePath": table_path, + "region": region + } + if coordinate_system is not None: + options['obsSpots']['coordinateSystem'] = coordinate_system + return options diff --git a/vitessce/utils.py b/vitessce/utils.py index b83d03f..aebe58c 100644 --- a/vitessce/utils.py +++ b/vitessce/utils.py @@ -1,11 +1,3 @@ -from __future__ import annotations - -from functools import partial -from typing import Optional - -import numpy as np - - def get_next_scope_numeric(prev_scopes): next_scope_int = 0 next_scope_str = None @@ -42,111 +34,3 @@ def get_initial_coordination_scope_name(dataset_uid, data_type, i=None): prefix = get_initial_coordination_scope_prefix(dataset_uid, data_type) return f"{prefix}{0 if i is None else i}" - -def gen_obs_embedding_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None, dims: Optional[list[list[int]]] = None): - if paths is not None: - if "obsEmbedding" not in options: - options["obsEmbedding"] = [] - if names is not None: - for key, mapping in zip(paths, names): - options["obsEmbedding"].append({ - "path": key, - "dims": [0, 1], - "embeddingType": mapping - }) - else: - for mapping in paths: - mapping_key = mapping.split('/')[-1] - options["obsEmbedding"].append({ - "path": mapping, - "dims": [0, 1], - "embeddingType": mapping_key - }) - if dims is not None: - if "obsEmbedding" not in options: - options["obsEmbedding"] = [] - for dim_i, dim in enumerate(dims): - options["obsEmbedding"][dim_i]['dims'] = dim - return options - - -def gen_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None): - if paths is not None: - options["obsSets"] = [] - if names is not None: - names = names - else: - names = [] - for obs in paths: - obs_end_path = obs.split('/')[-1] - first_letter_capitalized = obs_end_path.capitalize()[0] - names = [first_letter_capitalized + obs_end_path[1:]] - for obs, name in zip(paths, names): - options["obsSets"].append({ - "name": name, - "path": obs - }) - return options - - -def gen_obs_feature_matrix_schema(options: dict, matrix_path: Optional[str] = None, var_filter_path: Optional[str] = None, init_var_filter_path: Optional[str] = None): - if matrix_path is not None: - options["obsFeatureMatrix"] = { - "path": matrix_path - } - if var_filter_path is not None: - options["obsFeatureMatrix"]["featureFilterPath"] = var_filter_path - if init_var_filter_path is not None: - options["obsFeatureMatrix"]["initialFeatureFilterPath"] = init_var_filter_path - return options - - -def gen_obs_labels_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None): - if paths is not None: - if names is not None and len(paths) == len(names): - # A name was provided for each path element, so use those values. - names = names - else: - # Names were not provided for each path element, - # so fall back to using the final part of each path for the names. - names = [labels_path.split('/')[-1] for labels_path in paths] - obs_labels = [] - for path, name in zip(paths, names): - obs_labels.append({"path": path, "obsLabelsType": name}) - options["obsLabels"] = obs_labels - return options - - -def gen_path_schema(key: str, path: Optional[str], options: dict): - if path is not None: - options[key] = { - "path": path - } - return options - - -gen_obs_locations_schema = partial(gen_path_schema, "obsLocations") -gen_obs_segmentations_schema = partial(gen_path_schema, "obsSegmentations") -gen_obs_spots_schema = partial(gen_path_schema, "obsSpots") -gen_obs_points_schema = partial(gen_path_schema, "obsPoints") -gen_feature_labels_schema = partial(gen_path_schema, "featureLabels") - - -def gen_image_schema(options, path: str, affine_transformation: Optional[np.ndarray] = None) -> dict: - if path is not None: - options["image"] = { - "path": path - } - if affine_transformation is not None: - options['coordinateTransformations'] = affine_transformation - return options - - -def gen_obs_spots_from_shapes_schema(options: dict, shapes_path: Optional[str] = None, table_path: str = "tables/table") -> dict: - if shapes_path is not None: - options['obsSpots'] = { - "path": shapes_path, - "tablePath": table_path, - "region": "region" - } - return options diff --git a/vitessce/wrappers.py b/vitessce/wrappers.py index 6d40895..badf988 100644 --- a/vitessce/wrappers.py +++ b/vitessce/wrappers.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from collections import defaultdict import os from os.path import join @@ -13,7 +15,23 @@ import numpy as np from spatialdata import SpatialData -from vitessce.utils import gen_obs_locations_schema, gen_obs_segmentations_schema, gen_obs_spots_from_shapes_schema, gen_obs_spots_schema, gen_obs_points_schema, gen_obs_embedding_schema, gen_feature_labels_schema, gen_image_schema, gen_obs_feature_matrix_schema, gen_obs_labels_schema, gen_obs_sets_schema +if TYPE_CHECKING: + import lamindb as ln + +from vitessce.file_def_utils import ( + gen_obs_locations_schema, + gen_obs_segmentations_schema, + gen_sdata_obs_spots_schema, + gen_obs_spots_schema, + gen_obs_points_schema, + gen_obs_embedding_schema, + gen_feature_labels_schema, + gen_sdata_image_schema, + gen_sdata_labels_schema, + gen_obs_feature_matrix_schema, + gen_obs_labels_schema, + gen_obs_sets_schema +) from .constants import ( norm_enum, @@ -1037,18 +1055,33 @@ def image_file_def_creator(base_url): return image_file_def_creator -def raise_error_if_more_than_one_none(inputs): +def raise_error_if_zero_or_more_than_one(inputs): num_inputs = sum([1 for x in inputs if x is not None]) if num_inputs > 1: raise ValueError( - "Expected only one of adata_path, adata_url, or adata_store to be provided" + "Expected only one type of data input parameter to be provided (_url, _path, _store, etc.), but received more than one." ) if num_inputs == 0: raise ValueError( - "Expected one of adata_path, adata_url, or adata_store to be provided" + "Expected one type of data input parameter to be provided (_url, _path, _store, etc.), but received none." + ) + return True + +def raise_error_if_any(inputs): + num_inputs = sum([1 for x in inputs if x is not None]) + if num_inputs > 0: + raise ValueError( + "Did not expect any of these parameters to be provided, but received one or more: " + str(inputs) ) return True +def raise_error_if_more_than_one(inputs): + num_inputs = sum([1 for x in inputs if x is not None]) + if num_inputs > 1: + raise ValueError( + "Expected only one of these parameters to be provided, but received more than one: " + str(inputs) + ) + return True class AnnDataWrapper(AbstractWrapper): def __init__(self, adata_path=None, adata_url=None, adata_store=None, adata_artifact=None, ref_path=None, ref_url=None, ref_artifact=None, obs_feature_matrix_path=None, feature_filter_path=None, initial_feature_filter_path=None, obs_set_paths=None, obs_set_names=None, obs_locations_path=None, obs_segmentations_path=None, obs_embedding_paths=None, obs_embedding_names=None, obs_embedding_dims=None, obs_spots_path=None, obs_points_path=None, feature_labels_path=None, obs_labels_path=None, convert_to_dense=True, coordination_values=None, obs_labels_paths=None, obs_labels_names=None, **kwargs): @@ -1103,10 +1136,7 @@ def __init__(self, adata_path=None, adata_url=None, adata_store=None, adata_arti raise ValueError( "Did not expect reference JSON to be provided with adata_store") - num_inputs = sum([1 for x in [adata_path, adata_url, adata_store, adata_artifact] if x is not None]) - if num_inputs != 1: - raise ValueError( - "Expected one of adata_path, adata_url, adata_artifact, or adata_store to be provided") + raise_error_if_zero_or_more_than_one([adata_path, adata_url, adata_store, adata_artifact]) if adata_path is not None: self.is_remote = False @@ -1173,7 +1203,7 @@ def make_routes(self, dataset_uid, obj_i): if self.is_remote: return [] elif self.is_store: - self.register_zarr_store(dataset_uid, obj_i, self._store, self.local_dir_uid) + self.register_zarr_store(dataset_uid, obj_i, self._adata_store, self.local_dir_uid) return [] else: if self.is_h5ad: @@ -1186,9 +1216,9 @@ def make_routes(self, dataset_uid, obj_i): def get_zarr_url(self, base_url="", dataset_uid="", obj_i=""): if self.is_remote: - return self._url + return self._adata_url else: - return self.get_local_dir_url(base_url, dataset_uid, obj_i, self._path, self.local_dir_uid) + return self.get_local_dir_url(base_url, dataset_uid, obj_i, self._adata_path, self.local_dir_uid) def get_h5ad_url(self, base_url="", dataset_uid="", obj_i=""): if self.is_remote: @@ -1255,67 +1285,59 @@ def auto_view_config(self, vc): class SpatialDataWrapper(AnnDataWrapper): - def __init__(self, spatialdata_path: Optional[str] = None, spatialdata_url: Optional[str] = None, spatialdata_store: Optional[str] = None, image_elem: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None, shapes_elem: Optional[str] = None, labels_elem: Optional[str] = None, table_path: str = "tables/table", **kwargs): - """_summary_ - - Parameters - ---------- - spatialdata_path : Optional[str], optional - SpatialData path, exclusive with other `{spatialdata,adata}_xxxx` arguments, by default None - spatialdata_url : Optional[str], optional - SpatialData url, exclusive with other `{spatialdata,adata}_xxxx` arguments, by default None - spatialdata_store : Optional[str], optional - SpatialData store, exclusive with other `{spatialdata,adata}_xxxx` arguments, by default None - image_elem : Optional[str], optional - location of the image, by default None - affine_transformation : Optional[np.ndarray], optional - transformation to be applied to the image, by default None - shapes_elem : Optional[str], optional - location of the shapes, by default None - labels_elem : Optional[str], optional - location of the labels, by default None - - Raises - ------ - ValueError - If more than one of `{spatialdata,adata}_xxxx` is not `None` or all are. + def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = None, sdata_store: Optional[Union[str, zarr.storage.StoreLike]] = None, sdata_artifact: Optional[ln.Artifact] = None, image_path: Optional[str] = None, region: Optional[str] = None, coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None, spot_shapes_path: Optional[str] = None, labels_path: Optional[str] = None, table_path: str = "tables/table", **kwargs): """ - raise_error_if_more_than_one_none( - [ - spatialdata_path, + Wrap a SpatialData object. + + :param sdata_path: SpatialData path, exclusive with other `{sdata,adata}_xxxx` arguments, by default None + :type sdata_path: Optional[str] + :param sdata_url: SpatialData url, exclusive with other `{sdata,adata}_xxxx` arguments, by default None + :type sdata_url: Optional[str] + :param sdata_store: SpatialData store, exclusive with other `{spatialdata,adata}_xxxx` arguments, by default None + :type sdata_store: Optional[Union[str, zarr.storage.StoreLike]] + :param sdata_artifact: Artifact that corresponds to a SpatialData object. + :type sdata_artifact: Optional[ln.Artifact] + :param image_elem: Name of the image element of interest. By default, None. + :type image_elem: Optional[str] + :param coordinate_system: Name of a target coordinate system. + :type coordinate_system: Optional[str] + :param affine_transformation: Transformation to be applied to the image. By default, None. Prefer coordinate_system. + :type affine_transformation: Optional[np.ndarray] + :param shapes_elem: location of the shapes, by default None + :type shapes_elem: Optional[str] + :param labels_elem: location of the labels, by default None + :type labels_elem: Optional[str] + """ + raise_error_if_zero_or_more_than_one([ + sdata_path, + sdata_url, + sdata_store, + sdata_artifact, + ]) + raise_error_if_any([ kwargs.get('adata_path', None), - spatialdata_url, kwargs.get('adata_url', None), - spatialdata_store, - kwargs.get('adata_store', None) - ] - ) - super().__init__(adata_path=spatialdata_path, adata_url=spatialdata_url, adata_store=spatialdata_store, **kwargs) - self.local_dir_uid = make_unique_filename(".spatialdata.zarr") # correct? - self._image_elem = image_elem + kwargs.get('adata_store', None), + kwargs.get('adata_artifact', None) + ]) + super().__init__(adata_path=sdata_path, adata_url=sdata_url, adata_store=sdata_store, adata_artifact=sdata_artifact, **kwargs) + self.local_dir_uid = make_unique_filename(".sdata.zarr") + self._image_path = image_path + self._region = region + self._coordinate_system = coordinate_system self._affine_transformation = affine_transformation self._kwargs = kwargs - self._shapes_elem = shapes_elem - self._labels_elem = labels_elem - if self._path is not None and (self._url is not None): - raise ValueError( - "Did not expect path to be provided with url") - if self._url is None and (self._path is None): - raise ValueError( - "Expected either url or path to be provided") - if self._url is None: - self.is_remote = False + self._spot_shapes_path = spot_shapes_path + self._labels_path = labels_path + if self._adata_path is not None: self.zarr_folder = 'spatialdata.zarr' - else: - self.is_remote = True - self.zarr_folder = None self.obs_type_label = None if self._coordination_values is not None and "obsType" in self._coordination_values: self.obs_type_label = self._coordination_values["obsType"] self._table_path = table_path @classmethod - def from_object(cls: Type[SpatialDataWrapperType], spatialdata: SpatialData, table_keys_to_image_elems: dict[str, Union[str, None]] = defaultdict(type(None)), table_keys_to_regions: dict[str, Union[str, None]] = defaultdict(type(None)), obs_type_label: str = "spot") -> list[SpatialDataWrapperType]: + def from_object(cls: Type[SpatialDataWrapperType], sdata: SpatialData, table_keys_to_image_elems: dict[str, Union[str, None]] = defaultdict(type(None)), table_keys_to_regions: dict[str, Union[str, None]] = defaultdict(type(None)), obs_type_label: str = "spot") -> list[SpatialDataWrapperType]: """Instantiate a wrapper for SpatialData stores, one per table, directly from the SpatialData object. By default, we "show everything" that can reasonable be inferred given the information. If you wish to have more control, consider instantiating the object directly. This function will error if something cannot be inferred i.e., the user does not present @@ -1342,9 +1364,9 @@ def from_object(cls: Type[SpatialDataWrapperType], spatialdata: SpatialData, tab ValueError """ wrappers = [] - parent_table_key = "table" if (spatialdata.path / "table").exists() else "tables" - for table_key, table in spatialdata.tables.items(): - shapes_elem = None + parent_table_key = "table" if (sdata.path / "table").exists() else "tables" + for table_key, table in sdata.tables.items(): + spot_shapes_elem = None image_elem = table_keys_to_image_elems[table_key] labels_elem = None spatialdata_attr = table.uns['spatialdata_attrs'] @@ -1357,9 +1379,11 @@ def from_object(cls: Type[SpatialDataWrapperType], spatialdata: SpatialData, tab if len(region) > 1: raise ValueError("Vitessce cannot subset AnnData objects on the fly. Please provide an explicit region") region = region[0] - if region in spatialdata.shapes: - shapes_elem = f"shapes/{region}" - if region in spatialdata.labels: + if region in sdata.shapes: + spot_shapes_elem = f"shapes/{region}" + # Currently, only circle shapes are supported. + # TODO: add if statement to check that this region contains spot shapes rather than other types of shapes + if region in sdata.labels: labels_elem = f"labels/{region}" obs_feature_matrix_elem = f"{parent_table_key}/{table_key}/X" if 'highly_variable' in table.var: @@ -1370,14 +1394,14 @@ def from_object(cls: Type[SpatialDataWrapperType], spatialdata: SpatialData, tab obs_set_elems = [f"{parent_table_key}/{table_key}/obs/{elem}" for elem in table.obs if table.obs[elem].dtype == 'category'] wrappers += [ cls( - spatialdata_path=str(spatialdata.path), - image_elem=str(image_elem) if image_elem is not None else None, + sdata_path=str(sdata.path), + image_path=str(image_elem) if image_elem is not None else None, labels_path=str(labels_elem) if labels_elem is not None else None, obs_feature_matrix_path=str(obs_feature_matrix_elem), - shapes_elem=str(shapes_elem) if shapes_elem is not None else None, + spot_shapes_path=str(spot_shapes_elem) if spot_shapes_elem is not None else None, initial_feature_filter_path=initial_feature_filter_elem, obs_set_paths=obs_set_elems, - coordination_values={"obsType": "spot"} # TODO: should we remove? + coordination_values={"obsType": "spot"} # TODO: should we remove? ) ] return wrappers @@ -1388,10 +1412,9 @@ def generator(base_url): options = gen_obs_labels_schema(options, self._obs_labels_elems, self._obs_labels_names) options = gen_obs_feature_matrix_schema(options, self._expression_matrix, self._gene_var_filter, self._matrix_gene_var_filter) options = gen_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names) - if 'obsSets' in options: - options['obsSets'] = {'obsSets': options['obsSets']} # see https://github.com/vitessce/vitessce/blob/cd7e81956786a8130658d6745ff03986e2e6f806/packages/schemas/src/file-def-options.ts#L138-L146 for nested structure - options = gen_obs_spots_from_shapes_schema(options, self._shapes_elem, self._table_path) - options = gen_image_schema(options, self._image_elem, self._affine_transformation) + options = gen_sdata_obs_spots_schema(options, self._spot_shapes_path, self._table_path, self._region, self._coordinate_system) + options = gen_sdata_image_schema(options, self._image_path, self._coordinate_system, self._affine_transformation) + options = gen_sdata_labels_schema(options, self._labels_path, self._table_path, self._coordinate_system, self._affine_transformation) options = gen_feature_labels_schema(self._feature_labels, options) if len(options.keys()) > 0: obj_file_def = { From 1a3edd3262ebcf39ef06466452ffa8f438f9605b Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:26:57 -0400 Subject: [PATCH 2/7] Fix bugs --- docs/notebooks/spatial_data.ipynb | 123 ++++++++++-------------------- tests/test_wrappers.py | 3 +- vitessce/file_def_utils.py | 21 +++++ vitessce/wrappers.py | 13 ++-- 4 files changed, 69 insertions(+), 91 deletions(-) diff --git a/docs/notebooks/spatial_data.ipynb b/docs/notebooks/spatial_data.ipynb index af2da7a..1339f95 100644 --- a/docs/notebooks/spatial_data.ipynb +++ b/docs/notebooks/spatial_data.ipynb @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -96,22 +96,31 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 25, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/36/83j6x3ln225bvbpk1_vdzrm00000gn/T/ipykernel_65293/1452425863.py:1: DeprecationWarning: Table group found in zarr store at location /Users/mkeller/research/dbmi/vitessce/vitessce-python/docs/notebooks/data/visium.spatialdata.zarr. Please update the zarr store to use tables instead.\n", + " spatialdata = read_zarr(spatialdata_filepath)\n" + ] + } + ], "source": [ "spatialdata = read_zarr(spatialdata_filepath)" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "SpatialData object, with associated Zarr store: /Users/ilangold/Projects/Gehlenborg/vitessce-python/docs/notebooks/data/visium.spatialdata.zarr\n", + "SpatialData object, with associated Zarr store: /Users/mkeller/research/dbmi/vitessce/vitessce-python/docs/notebooks/data/visium.spatialdata.zarr\n", "├── Images\n", "│ ├── 'CytAssist_FFPE_Human_Breast_Cancer_full_image': DataTree[cyx] (3, 21571, 19505), (3, 10785, 9752), (3, 5392, 4876), (3, 2696, 2438), (3, 1348, 1219)\n", "│ ├── 'CytAssist_FFPE_Human_Breast_Cancer_hires_image': DataArray[cyx] (3, 2000, 1809)\n", @@ -126,10 +135,14 @@ " ▸ 'downscaled_lowres', with elements:\n", " CytAssist_FFPE_Human_Breast_Cancer_lowres_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)\n", " ▸ 'global', with elements:\n", - " CytAssist_FFPE_Human_Breast_Cancer_full_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)" + " CytAssist_FFPE_Human_Breast_Cancer_full_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)\n", + "with the following elements not in the Zarr store:\n", + " ▸ table (Tables)\n", + "with the following elements in the Zarr store but not in the SpatialData object:\n", + " ▸ table (Table)" ] }, - "execution_count": 17, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -158,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -180,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -192,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -201,16 +214,16 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 83, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -224,6 +237,11 @@ " 'photometricInterpretation': 'RGB',\n", " }]),\n", "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'spotLayer': CL([{\n", + " 'obsType': 'spot',\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"obsSpots\"))\n", "obs_sets = vc.add_view(cm.OBS_SETS, dataset=dataset)\n", "vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], [wrapper.obs_type_label])" ] @@ -239,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -248,7 +266,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 33, "metadata": { "scrolled": true }, @@ -256,7 +274,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "", + "model_id": "5146584fc92e4debb2e6624d13e62f05", "version_major": 2, "version_minor": 1 }, @@ -264,7 +282,7 @@ "VitessceWidget(config={'version': '1.0.16', 'name': 'Visium SpatialData Demo (visium_associated_xenium_io)', '…" ] }, - "execution_count": 85, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -276,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -285,72 +303,9 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'version': '1.0.16',\n", - " 'name': 'Visium SpatialData Demo (visium_associated_xenium_io)',\n", - " 'description': 'From https://spatialdata.scverse.org/en/latest/tutorials/notebooks/datasets/README.html',\n", - " 'datasets': [{'uid': 'A',\n", - " 'name': 'Breast Cancer Visium',\n", - " 'files': [{'fileType': 'spatialdata.zarr',\n", - " 'url': '/A/0/bdccdfab-49c3-4dad-8c85-aaca4cf984f6.spatialdata.zarr',\n", - " 'options': {'obsFeatureMatrix': {'path': 'tables/table/X'},\n", - " 'obsSets': {'obsSets': [{'name': 'Region',\n", - " 'path': 'tables/table/obs/region'}],\n", - " 'tablePath': 'tables/table'},\n", - " 'obsSpots': {'path': 'shapes/CytAssist_FFPE_Human_Breast_Cancer',\n", - " 'tablePath': 'tables/table',\n", - " 'region': 'region'},\n", - " 'image': {'path': 'images/CytAssist_FFPE_Human_Breast_Cancer_full_image'}},\n", - " 'coordinationValues': {'obsType': 'spot'}}]}],\n", - " 'coordinationSpace': {'dataset': {'A': 'A'},\n", - " 'imageLayer': {'init_A_image_0': '__dummy__'},\n", - " 'photometricInterpretation': {'init_A_image_0': 'RGB'},\n", - " 'metaCoordinationScopes': {'init_A_image_0': {'imageLayer': ['init_A_image_0']}},\n", - " 'metaCoordinationScopesBy': {'init_A_image_0': {'imageLayer': {'photometricInterpretation': {'init_A_image_0': 'init_A_image_0'}}}},\n", - " 'obsType': {'A': 'spot'}},\n", - " 'layout': [{'component': 'spatialBeta',\n", - " 'coordinationScopes': {'dataset': 'A',\n", - " 'metaCoordinationScopes': ['init_A_image_0'],\n", - " 'metaCoordinationScopesBy': ['init_A_image_0'],\n", - " 'obsType': 'A'},\n", - " 'x': 0.0,\n", - " 'y': 0,\n", - " 'w': 6.0,\n", - " 'h': 12},\n", - " {'component': 'featureList',\n", - " 'coordinationScopes': {'dataset': 'A', 'obsType': 'A'},\n", - " 'x': 6.0,\n", - " 'y': 0.0,\n", - " 'w': 6.0,\n", - " 'h': 3.0},\n", - " {'component': 'layerControllerBeta',\n", - " 'coordinationScopes': {'dataset': 'A',\n", - " 'metaCoordinationScopes': ['init_A_image_0'],\n", - " 'metaCoordinationScopesBy': ['init_A_image_0'],\n", - " 'obsType': 'A'},\n", - " 'x': 6.0,\n", - " 'y': 3.0,\n", - " 'w': 6.0,\n", - " 'h': 3.0},\n", - " {'component': 'obsSets',\n", - " 'coordinationScopes': {'dataset': 'A', 'obsType': 'A'},\n", - " 'x': 6.0,\n", - " 'y': 6.0,\n", - " 'w': 6.0,\n", - " 'h': 6.0}],\n", - " 'initStrategy': 'auto'}" - ] - }, - "execution_count": 86, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "vc.to_dict(\"\")" ] diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index f2b525e..1e86a23 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -401,5 +401,6 @@ def test_spatial_data_with_base_dir(self): file_def_creator = w.make_file_def_creator('A', 0) file_def = file_def_creator('http://localhost:8000') + print(file_def) self.assertEqual(file_def, - {'fileType': 'spatialdata.zarr', 'url': 'http://localhost:8000/test.spatialdata.zarr', 'options': {'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}], 'image': {'path': 'images/picture'}}}) + {'fileType': 'spatialdata.zarr', 'url': 'http://localhost:8000/test.spatialdata.zarr', 'options': {'obsSets': {'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}], 'tablePath': 'tables/table'}, 'image': {'path': 'images/picture'}}}) diff --git a/vitessce/file_def_utils.py b/vitessce/file_def_utils.py index 7484acd..6019982 100644 --- a/vitessce/file_def_utils.py +++ b/vitessce/file_def_utils.py @@ -50,6 +50,27 @@ def gen_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: }) return options +def gen_sdata_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None, table_path: Optional[str] = None, region: Optional[str] = None): + if paths is not None: + options["obsSets"] = { "obsSets": [] } + if names is not None: + names = names + else: + names = [] + for obs in paths: + obs_end_path = obs.split('/')[-1] + names += [obs_end_path] + for obs, name in zip(paths, names): + options["obsSets"]["obsSets"].append({ + "name": name, + "path": obs + }) + if table_path is not None: + options["obsSets"]["tablePath"] = table_path + if region is not None: + options["obsSets"]["region"] = region + return options + def gen_obs_feature_matrix_schema(options: dict, matrix_path: Optional[str] = None, var_filter_path: Optional[str] = None, init_var_filter_path: Optional[str] = None): if matrix_path is not None: diff --git a/vitessce/wrappers.py b/vitessce/wrappers.py index badf988..704a33c 100644 --- a/vitessce/wrappers.py +++ b/vitessce/wrappers.py @@ -21,16 +21,17 @@ from vitessce.file_def_utils import ( gen_obs_locations_schema, gen_obs_segmentations_schema, - gen_sdata_obs_spots_schema, gen_obs_spots_schema, gen_obs_points_schema, gen_obs_embedding_schema, gen_feature_labels_schema, - gen_sdata_image_schema, - gen_sdata_labels_schema, gen_obs_feature_matrix_schema, gen_obs_labels_schema, - gen_obs_sets_schema + gen_obs_sets_schema, + gen_sdata_image_schema, + gen_sdata_labels_schema, + gen_sdata_obs_spots_schema, + gen_sdata_obs_sets_schema ) from .constants import ( @@ -1240,7 +1241,7 @@ def get_anndata_zarr(base_url): options = gen_obs_spots_schema(self._spatial_spots_obsm, options) options = gen_obs_points_schema(self._spatial_points_obsm, options) options = gen_obs_embedding_schema(options, self._mappings_obsm, self._mappings_obsm_names, self._mappings_obsm_dims) - options = gen_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names) + options = gen_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names,) options = gen_obs_feature_matrix_schema(options, self._expression_matrix, self._gene_var_filter, self._matrix_gene_var_filter) options = gen_feature_labels_schema(self._feature_labels, options) options = gen_obs_labels_schema(options, self._obs_labels_elems, self._obs_labels_names) @@ -1411,7 +1412,7 @@ def generator(base_url): options = {} options = gen_obs_labels_schema(options, self._obs_labels_elems, self._obs_labels_names) options = gen_obs_feature_matrix_schema(options, self._expression_matrix, self._gene_var_filter, self._matrix_gene_var_filter) - options = gen_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names) + options = gen_sdata_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names, self._table_path, self._region) options = gen_sdata_obs_spots_schema(options, self._spot_shapes_path, self._table_path, self._region, self._coordinate_system) options = gen_sdata_image_schema(options, self._image_path, self._coordinate_system, self._affine_transformation) options = gen_sdata_labels_schema(options, self._labels_path, self._table_path, self._coordinate_system, self._affine_transformation) From 9a43e634eb2aa7744699d18e40b85c6e41e9d1d6 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:28:02 -0400 Subject: [PATCH 3/7] Lint --- vitessce/file_def_utils.py | 5 ++++- vitessce/utils.py | 1 - vitessce/wrappers.py | 23 +++++++++++++---------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/vitessce/file_def_utils.py b/vitessce/file_def_utils.py index 6019982..ece8508 100644 --- a/vitessce/file_def_utils.py +++ b/vitessce/file_def_utils.py @@ -50,9 +50,10 @@ def gen_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: }) return options + def gen_sdata_obs_sets_schema(options: dict, paths: Optional[list[str]] = None, names: Optional[list[str]] = None, table_path: Optional[str] = None, region: Optional[str] = None): if paths is not None: - options["obsSets"] = { "obsSets": [] } + options["obsSets"] = {"obsSets": []} if names is not None: names = names else: @@ -114,6 +115,7 @@ def gen_path_schema(key: str, path: Optional[str], options: dict): gen_obs_points_schema = partial(gen_path_schema, "obsPoints") gen_feature_labels_schema = partial(gen_path_schema, "featureLabels") + def gen_sdata_image_schema(options, path: str, coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None) -> dict: if path is not None: options["image"] = { @@ -125,6 +127,7 @@ def gen_sdata_image_schema(options, path: str, coordinate_system: Optional[str] options["image"]['coordinateSystem'] = coordinate_system return options + def gen_sdata_labels_schema(options, path: str, table_path: str = "tables/table", coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None) -> dict: if path is not None: options["labels"] = { diff --git a/vitessce/utils.py b/vitessce/utils.py index aebe58c..fdb31c2 100644 --- a/vitessce/utils.py +++ b/vitessce/utils.py @@ -33,4 +33,3 @@ def get_initial_coordination_scope_prefix(dataset_uid, data_type): def get_initial_coordination_scope_name(dataset_uid, data_type, i=None): prefix = get_initial_coordination_scope_prefix(dataset_uid, data_type) return f"{prefix}{0 if i is None else i}" - diff --git a/vitessce/wrappers.py b/vitessce/wrappers.py index 704a33c..85c19b2 100644 --- a/vitessce/wrappers.py +++ b/vitessce/wrappers.py @@ -1068,6 +1068,7 @@ def raise_error_if_zero_or_more_than_one(inputs): ) return True + def raise_error_if_any(inputs): num_inputs = sum([1 for x in inputs if x is not None]) if num_inputs > 0: @@ -1076,6 +1077,7 @@ def raise_error_if_any(inputs): ) return True + def raise_error_if_more_than_one(inputs): num_inputs = sum([1 for x in inputs if x is not None]) if num_inputs > 1: @@ -1084,6 +1086,7 @@ def raise_error_if_more_than_one(inputs): ) return True + class AnnDataWrapper(AbstractWrapper): def __init__(self, adata_path=None, adata_url=None, adata_store=None, adata_artifact=None, ref_path=None, ref_url=None, ref_artifact=None, obs_feature_matrix_path=None, feature_filter_path=None, initial_feature_filter_path=None, obs_set_paths=None, obs_set_names=None, obs_locations_path=None, obs_segmentations_path=None, obs_embedding_paths=None, obs_embedding_names=None, obs_embedding_dims=None, obs_spots_path=None, obs_points_path=None, feature_labels_path=None, obs_labels_path=None, convert_to_dense=True, coordination_values=None, obs_labels_paths=None, obs_labels_names=None, **kwargs): """ @@ -1303,23 +1306,23 @@ def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = :param coordinate_system: Name of a target coordinate system. :type coordinate_system: Optional[str] :param affine_transformation: Transformation to be applied to the image. By default, None. Prefer coordinate_system. - :type affine_transformation: Optional[np.ndarray] + :type affine_transformation: Optional[np.ndarray] :param shapes_elem: location of the shapes, by default None :type shapes_elem: Optional[str] :param labels_elem: location of the labels, by default None :type labels_elem: Optional[str] """ raise_error_if_zero_or_more_than_one([ - sdata_path, - sdata_url, - sdata_store, - sdata_artifact, + sdata_path, + sdata_url, + sdata_store, + sdata_artifact, ]) raise_error_if_any([ - kwargs.get('adata_path', None), - kwargs.get('adata_url', None), - kwargs.get('adata_store', None), - kwargs.get('adata_artifact', None) + kwargs.get('adata_path', None), + kwargs.get('adata_url', None), + kwargs.get('adata_store', None), + kwargs.get('adata_artifact', None) ]) super().__init__(adata_path=sdata_path, adata_url=sdata_url, adata_store=sdata_store, adata_artifact=sdata_artifact, **kwargs) self.local_dir_uid = make_unique_filename(".sdata.zarr") @@ -1402,7 +1405,7 @@ def from_object(cls: Type[SpatialDataWrapperType], sdata: SpatialData, table_key spot_shapes_path=str(spot_shapes_elem) if spot_shapes_elem is not None else None, initial_feature_filter_path=initial_feature_filter_elem, obs_set_paths=obs_set_elems, - coordination_values={"obsType": "spot"} # TODO: should we remove? + coordination_values={"obsType": "spot"} # TODO: should we remove? ) ] return wrappers From 5b62f64e28c89bb083575aae1d47200278f1050a Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:28:29 -0400 Subject: [PATCH 4/7] Update --- docs/notebooks/spatial_data.ipynb | 105 +++++------------------------- 1 file changed, 15 insertions(+), 90 deletions(-) diff --git a/docs/notebooks/spatial_data.ipynb b/docs/notebooks/spatial_data.ipynb index 1339f95..6e14a24 100644 --- a/docs/notebooks/spatial_data.ipynb +++ b/docs/notebooks/spatial_data.ipynb @@ -27,18 +27,9 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -70,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -96,57 +87,18 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/36/83j6x3ln225bvbpk1_vdzrm00000gn/T/ipykernel_65293/1452425863.py:1: DeprecationWarning: Table group found in zarr store at location /Users/mkeller/research/dbmi/vitessce/vitessce-python/docs/notebooks/data/visium.spatialdata.zarr. Please update the zarr store to use tables instead.\n", - " spatialdata = read_zarr(spatialdata_filepath)\n" - ] - } - ], + "outputs": [], "source": [ "spatialdata = read_zarr(spatialdata_filepath)" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "SpatialData object, with associated Zarr store: /Users/mkeller/research/dbmi/vitessce/vitessce-python/docs/notebooks/data/visium.spatialdata.zarr\n", - "├── Images\n", - "│ ├── 'CytAssist_FFPE_Human_Breast_Cancer_full_image': DataTree[cyx] (3, 21571, 19505), (3, 10785, 9752), (3, 5392, 4876), (3, 2696, 2438), (3, 1348, 1219)\n", - "│ ├── 'CytAssist_FFPE_Human_Breast_Cancer_hires_image': DataArray[cyx] (3, 2000, 1809)\n", - "│ └── 'CytAssist_FFPE_Human_Breast_Cancer_lowres_image': DataArray[cyx] (3, 600, 543)\n", - "├── Shapes\n", - "│ └── 'CytAssist_FFPE_Human_Breast_Cancer': GeoDataFrame shape: (4992, 2) (2D shapes)\n", - "└── Tables\n", - " └── 'table': AnnData (4992, 18085)\n", - "with coordinate systems:\n", - " ▸ 'downscaled_hires', with elements:\n", - " CytAssist_FFPE_Human_Breast_Cancer_hires_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)\n", - " ▸ 'downscaled_lowres', with elements:\n", - " CytAssist_FFPE_Human_Breast_Cancer_lowres_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)\n", - " ▸ 'global', with elements:\n", - " CytAssist_FFPE_Human_Breast_Cancer_full_image (Images), CytAssist_FFPE_Human_Breast_Cancer (Shapes)\n", - "with the following elements not in the Zarr store:\n", - " ▸ table (Tables)\n", - "with the following elements in the Zarr store but not in the SpatialData object:\n", - " ▸ table (Table)" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "spatialdata" ] @@ -171,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -193,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -205,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -214,20 +166,9 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", "feature_list = vc.add_view(cm.FEATURE_LIST, dataset=dataset)\n", @@ -257,7 +198,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -266,27 +207,11 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5146584fc92e4debb2e6624d13e62f05", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "VitessceWidget(config={'version': '1.0.16', 'name': 'Visium SpatialData Demo (visium_associated_xenium_io)', '…" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "vw = vc.widget()\n", "vw" From 5d95665e0b6e2f2273d16169386555c9edb067f3 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:16:52 -0400 Subject: [PATCH 5/7] Omit --- .coveragerc_omit | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc_omit b/.coveragerc_omit index 96a2463..b203626 100644 --- a/.coveragerc_omit +++ b/.coveragerc_omit @@ -2,6 +2,7 @@ omit = vitessce/config.py vitessce/export.py + vitessce/file_def_utils.py vitessce/routes.py vitessce/widget.py vitessce/wrappers.py From 3f10a28ca2f67b39d799a2884ee3919bf5eb0c3e Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:02:33 -0400 Subject: [PATCH 6/7] Add test --- tests/test_wrappers.py | 66 +++++++++++++++++++++++++++++++++++--- vitessce/file_def_utils.py | 21 ++++++++++-- vitessce/widget.py | 6 ++-- vitessce/wrappers.py | 25 ++++++++------- 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 1e86a23..4efde27 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -394,13 +394,71 @@ def test_multivec_zarr_with_base_dir(self): def test_spatial_data_with_base_dir(self): spatial_data_path = 'test.spatialdata.zarr' - w = SpatialDataWrapper(sdata_path=spatial_data_path, image_path="images/picture", obs_set_paths=['obs/CellType'], obs_set_names=['Cell Type'], obs_embedding_paths=[ - 'obsm/X_umap'], obs_embedding_names=['UMAP']) + w = SpatialDataWrapper( + sdata_path=spatial_data_path, + image_path="images/picture", + obs_set_paths=['obs/CellType'], + obs_set_names=['Cell Type'], + obs_embedding_paths=['obsm/X_umap'], + obs_embedding_names=['UMAP'] + ) w.base_dir = data_path w.local_dir_uid = 'spatialdata.zarr' file_def_creator = w.make_file_def_creator('A', 0) file_def = file_def_creator('http://localhost:8000') print(file_def) - self.assertEqual(file_def, - {'fileType': 'spatialdata.zarr', 'url': 'http://localhost:8000/test.spatialdata.zarr', 'options': {'obsSets': {'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}], 'tablePath': 'tables/table'}, 'image': {'path': 'images/picture'}}}) + self.assertEqual(file_def, { + 'fileType': 'spatialdata.zarr', + 'url': 'http://localhost:8000/test.spatialdata.zarr', + 'options': { + 'obsSets': { + 'obsSets': [{'name': 'Cell Type', 'path': 'obs/CellType'}], + 'tablePath': 'tables/table' + }, + 'image': {'path': 'images/picture'} + }}) + + def test_spatial_data_with_base_dir_2(self): + + spatial_data_path = 'test.spatialdata.zarr' + w = SpatialDataWrapper( + sdata_path=spatial_data_path, + image_path='images/CytAssist_FFPE_Human_Breast_Cancer_full_image', + coordinate_system='aligned', + region='CytAssist_FFPE_Human_Breast_Cancer', + obs_feature_matrix_path='tables/table/X', + obs_spots_path='shapes/CytAssist_FFPE_Human_Breast_Cancer', + table_path='tables/table', + coordination_values={ + "obsType": "spot" + } + ) + w.base_dir = data_path + w.local_dir_uid = 'spatialdata.zarr' + + file_def_creator = w.make_file_def_creator('A', 0) + file_def = file_def_creator('http://localhost:8000') + self.assertDictEqual(file_def, { + 'fileType': 'spatialdata.zarr', + 'url': 'http://localhost:8000/test.spatialdata.zarr', + 'options': { + 'image': { + 'path': 'images/CytAssist_FFPE_Human_Breast_Cancer_full_image', + 'coordinateSystem': 'aligned', + }, + 'obsFeatureMatrix': { + 'path': 'tables/table/X', + 'region': 'CytAssist_FFPE_Human_Breast_Cancer' + }, + 'obsSpots': { + 'path': 'shapes/CytAssist_FFPE_Human_Breast_Cancer', + 'tablePath': 'tables/table', + 'region': 'CytAssist_FFPE_Human_Breast_Cancer', + 'coordinateSystem': 'aligned', + } + }, + 'coordinationValues': { + "obsType": "spot" + } + }) diff --git a/vitessce/file_def_utils.py b/vitessce/file_def_utils.py index ece8508..4723bc6 100644 --- a/vitessce/file_def_utils.py +++ b/vitessce/file_def_utils.py @@ -142,13 +142,28 @@ def gen_sdata_labels_schema(options, path: str, table_path: str = "tables/table" return options -def gen_sdata_obs_spots_schema(options: dict, shapes_path: Optional[str] = None, table_path: str = "tables/table", region: Optional[str] = None, coordinate_system: Optional[str] = None) -> dict: +def gen_sdata_obs_spots_schema(options: dict, shapes_path: str, table_path: str = "tables/table", region: Optional[str] = None, coordinate_system: Optional[str] = None) -> dict: if shapes_path is not None: options['obsSpots'] = { "path": shapes_path, - "tablePath": table_path, - "region": region + "tablePath": table_path } + if region is not None: + options['obsSpots']['region'] = region if coordinate_system is not None: options['obsSpots']['coordinateSystem'] = coordinate_system return options + + +def gen_sdata_obs_feature_matrix_schema(options: dict, matrix_path: Optional[str] = None, var_filter_path: Optional[str] = None, init_var_filter_path: Optional[str] = None, region: Optional[str] = None): + if matrix_path is not None: + options["obsFeatureMatrix"] = { + "path": matrix_path + } + if region is not None: + options['obsFeatureMatrix']['region'] = region + if var_filter_path is not None: + options["obsFeatureMatrix"]["featureFilterPath"] = var_filter_path + if init_var_filter_path is not None: + options["obsFeatureMatrix"]["initialFeatureFilterPath"] = init_var_filter_path + return options diff --git a/vitessce/widget.py b/vitessce/widget.py index b38da7e..b37df86 100644 --- a/vitessce/widget.py +++ b/vitessce/widget.py @@ -454,7 +454,7 @@ class VitessceWidget(anywidget.AnyWidget): next_port = DEFAULT_PORT - js_package_version = Unicode('3.4.12').tag(sync=True) + js_package_version = Unicode('3.4.13').tag(sync=True) js_dev_mode = Bool(False).tag(sync=True) custom_js_url = Unicode('').tag(sync=True) plugin_esm = List(trait=Unicode(''), default_value=[]).tag(sync=True) @@ -463,7 +463,7 @@ class VitessceWidget(anywidget.AnyWidget): store_urls = List(trait=Unicode(''), default_value=[]).tag(sync=True) - def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.4.12', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, invoke_timeout=30000): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.4.13', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, invoke_timeout=30000): """ Construct a new Vitessce widget. @@ -576,7 +576,7 @@ def _plugin_command(self, params, buffers): # Launch Vitessce using plain HTML representation (no ipywidgets) -def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.4.12', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): +def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.4.13', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): from IPython.display import display, HTML uid_str = "vitessce" + get_uid_str(uid) diff --git a/vitessce/wrappers.py b/vitessce/wrappers.py index 85c19b2..fda7345 100644 --- a/vitessce/wrappers.py +++ b/vitessce/wrappers.py @@ -31,7 +31,8 @@ gen_sdata_image_schema, gen_sdata_labels_schema, gen_sdata_obs_spots_schema, - gen_sdata_obs_sets_schema + gen_sdata_obs_sets_schema, + gen_sdata_obs_feature_matrix_schema, ) from .constants import ( @@ -1289,7 +1290,7 @@ def auto_view_config(self, vc): class SpatialDataWrapper(AnnDataWrapper): - def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = None, sdata_store: Optional[Union[str, zarr.storage.StoreLike]] = None, sdata_artifact: Optional[ln.Artifact] = None, image_path: Optional[str] = None, region: Optional[str] = None, coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None, spot_shapes_path: Optional[str] = None, labels_path: Optional[str] = None, table_path: str = "tables/table", **kwargs): + def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = None, sdata_store: Optional[Union[str, zarr.storage.StoreLike]] = None, sdata_artifact: Optional[ln.Artifact] = None, image_path: Optional[str] = None, region: Optional[str] = None, coordinate_system: Optional[str] = None, affine_transformation: Optional[np.ndarray] = None, obs_spots_path: Optional[str] = None, labels_path: Optional[str] = None, table_path: str = "tables/table", **kwargs): """ Wrap a SpatialData object. @@ -1301,16 +1302,16 @@ def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = :type sdata_store: Optional[Union[str, zarr.storage.StoreLike]] :param sdata_artifact: Artifact that corresponds to a SpatialData object. :type sdata_artifact: Optional[ln.Artifact] - :param image_elem: Name of the image element of interest. By default, None. - :type image_elem: Optional[str] + :param image_path: Path to the image element of interest. By default, None. + :type image_path: Optional[str] :param coordinate_system: Name of a target coordinate system. :type coordinate_system: Optional[str] :param affine_transformation: Transformation to be applied to the image. By default, None. Prefer coordinate_system. :type affine_transformation: Optional[np.ndarray] - :param shapes_elem: location of the shapes, by default None - :type shapes_elem: Optional[str] - :param labels_elem: location of the labels, by default None - :type labels_elem: Optional[str] + :param obs_spots_path: Location of shapes that should be interpreted as spot observations, by default None + :type obs_spots_path: Optional[str] + :param labels_path: Location of the labels (segmentation bitmask image), by default None + :type labels_path: Optional[str] """ raise_error_if_zero_or_more_than_one([ sdata_path, @@ -1331,7 +1332,7 @@ def __init__(self, sdata_path: Optional[str] = None, sdata_url: Optional[str] = self._coordinate_system = coordinate_system self._affine_transformation = affine_transformation self._kwargs = kwargs - self._spot_shapes_path = spot_shapes_path + self._obs_spots_path = obs_spots_path self._labels_path = labels_path if self._adata_path is not None: self.zarr_folder = 'spatialdata.zarr' @@ -1402,7 +1403,7 @@ def from_object(cls: Type[SpatialDataWrapperType], sdata: SpatialData, table_key image_path=str(image_elem) if image_elem is not None else None, labels_path=str(labels_elem) if labels_elem is not None else None, obs_feature_matrix_path=str(obs_feature_matrix_elem), - spot_shapes_path=str(spot_shapes_elem) if spot_shapes_elem is not None else None, + obs_spots_path=str(spot_shapes_elem) if spot_shapes_elem is not None else None, initial_feature_filter_path=initial_feature_filter_elem, obs_set_paths=obs_set_elems, coordination_values={"obsType": "spot"} # TODO: should we remove? @@ -1414,9 +1415,9 @@ def make_file_def_creator(self, dataset_uid: str, obj_i: str) -> Optional[Callab def generator(base_url): options = {} options = gen_obs_labels_schema(options, self._obs_labels_elems, self._obs_labels_names) - options = gen_obs_feature_matrix_schema(options, self._expression_matrix, self._gene_var_filter, self._matrix_gene_var_filter) + options = gen_sdata_obs_feature_matrix_schema(options, self._expression_matrix, self._gene_var_filter, self._matrix_gene_var_filter, self._region) options = gen_sdata_obs_sets_schema(options, self._obs_set_elems, self._obs_set_names, self._table_path, self._region) - options = gen_sdata_obs_spots_schema(options, self._spot_shapes_path, self._table_path, self._region, self._coordinate_system) + options = gen_sdata_obs_spots_schema(options, self._obs_spots_path, self._table_path, self._region, self._coordinate_system) options = gen_sdata_image_schema(options, self._image_path, self._coordinate_system, self._affine_transformation) options = gen_sdata_labels_schema(options, self._labels_path, self._table_path, self._coordinate_system, self._affine_transformation) options = gen_feature_labels_schema(self._feature_labels, options) From 7c5fa6b17e940264b9b16acaeebb636a949dac43 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:09:49 -0400 Subject: [PATCH 7/7] JS version --- vitessce/widget.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vitessce/widget.py b/vitessce/widget.py index b37df86..4b34473 100644 --- a/vitessce/widget.py +++ b/vitessce/widget.py @@ -454,7 +454,7 @@ class VitessceWidget(anywidget.AnyWidget): next_port = DEFAULT_PORT - js_package_version = Unicode('3.4.13').tag(sync=True) + js_package_version = Unicode('3.4.14').tag(sync=True) js_dev_mode = Bool(False).tag(sync=True) custom_js_url = Unicode('').tag(sync=True) plugin_esm = List(trait=Unicode(''), default_value=[]).tag(sync=True) @@ -463,7 +463,7 @@ class VitessceWidget(anywidget.AnyWidget): store_urls = List(trait=Unicode(''), default_value=[]).tag(sync=True) - def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.4.13', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, invoke_timeout=30000): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.4.14', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, invoke_timeout=30000): """ Construct a new Vitessce widget. @@ -576,7 +576,7 @@ def _plugin_command(self, params, buffers): # Launch Vitessce using plain HTML representation (no ipywidgets) -def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.4.13', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): +def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.4.14', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): from IPython.display import display, HTML uid_str = "vitessce" + get_uid_str(uid)