diff --git a/tests/create_test_data.py b/tests/create_test_data.py index 2b080fee..3cb7298b 100644 --- a/tests/create_test_data.py +++ b/tests/create_test_data.py @@ -139,10 +139,11 @@ def create_test_anndata_file(h5ad_path): 'exPFC2', 'GABA2' ] + obs_cell_label_arr = [f'{l}-label' for l in obs_celltype_arr] obs_df = pd.DataFrame( data=[ - {'index': i, 'CellType': ct} - for i, ct in zip(obs_index_arr, obs_celltype_arr) + {'index': i, 'CellType': ct, 'CellLabel': cl} + for i, ct, cl in zip(obs_index_arr, obs_celltype_arr, obs_cell_label_arr) ] ) obsm = {"X_umap": np.array([[0, 1] for c in obs_index_arr])} diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 37bf326a..16cededa 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -141,8 +141,10 @@ def test_ome_zarr_with_base_dir(self): def test_anndata(self): adata_path = data_path / 'test.h5ad.zarr' - w = AnnDataWrapper(adata_path, obs_set_paths=['obs/CellType'], obs_set_names=['Cell Type'], obs_embedding_paths=[ - 'obsm/X_umap'], obs_embedding_names=['UMAP']) + w = AnnDataWrapper(adata_path, + obs_set_paths=['obs/CellType'], obs_set_names=['Cell Type'], + obs_labels_names=['Cell Label'], obs_labels_paths=['obs/CellLabel'], + obs_embedding_paths=['obsm/X_umap'], obs_embedding_names=['UMAP']) w.local_dir_uid = 'anndata.zarr' file_def_creator = w.make_file_def_creator('A', 0) @@ -150,7 +152,8 @@ def test_anndata(self): self.assertEqual(file_def, {'fileType': 'anndata.zarr', 'url': 'http://localhost:8000/A/0/anndata.zarr', 'options': { 'obsEmbedding': [{'path': 'obsm/X_umap', 'embeddingType': 'UMAP', 'dims': [0, 1]}], - 'obsSets': [{'path': 'obs/CellType', 'name': 'Cell Type'}] + 'obsSets': [{'path': 'obs/CellType', 'name': 'Cell Type'}], + 'obsLabels': [{'path': 'obs/CellLabel', 'obsLabelsType': 'Cell Label'}] }}) def test_anndata_with_base_dir(self): diff --git a/vitessce/wrappers.py b/vitessce/wrappers.py index 999b10d5..308b416b 100644 --- a/vitessce/wrappers.py +++ b/vitessce/wrappers.py @@ -506,7 +506,7 @@ def create_image_json(self, img_url): class AnnDataWrapper(AbstractWrapper): - def __init__(self, adata_path=None, adata_url=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, request_init=None, feature_labels_path=None, obs_labels_path=None, convert_to_dense=True, coordination_values=None, **kwargs): + def __init__(self, adata_path=None, adata_url=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, request_init=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): """ Wrap an AnnData object by creating an instance of the ``AnnDataWrapper`` class. @@ -525,6 +525,8 @@ def __init__(self, adata_path=None, adata_url=None, obs_feature_matrix_path=None :param dict request_init: options to be passed along with every fetch request from the browser, like { "header": { "Authorization": "Bearer dsfjalsdfa1431" } } :param str feature_labels_path: The name of a column containing feature labels (e.g., alternate gene symbols), instead of the default index in `var` of the AnnData store. :param str obs_labels_path: The name of a column containing observation labels (e.g., alternate cell IDs), instead of the default index in `obs` of the AnnData store. + :param list[str] obs_labels_paths: The names of columns containing observation labels (e.g., alternate cell IDs), instead of the default index in `obs` of the AnnData store. + :param list[str] obs_labels_names: The optional display names of columns containing observation labels (e.g., alternate cell IDs), instead of the default index in `obs` of the AnnData store. :param bool convert_to_dense: Whether or not to convert `X` to dense the zarr store (dense is faster but takes more disk space). :param coordination_values: Coordination values for the file definition. :type coordination_values: dict or None @@ -559,7 +561,13 @@ def __init__(self, adata_path=None, adata_url=None, obs_feature_matrix_path=None self._mappings_obsm_dims = obs_embedding_dims self._request_init = request_init self._gene_alias = feature_labels_path - self._obs_labels_path = obs_labels_path + # Support legacy provision of single obs labels path + if (obs_labels_path is not None): + self._obs_labels_paths = [obs_labels_path] + self._obs_labels_names = [obs_labels_path] + else: + self._obs_labels_paths = obs_labels_paths + self._obs_labels_names = obs_labels_names self._convert_to_dense = convert_to_dense self._coordination_values = coordination_values @@ -642,10 +650,11 @@ def get_anndata_zarr(base_url): options["featureLabels"] = { "path": self._gene_alias } - if self._obs_labels_path is not None: - options["obsLabels"] = { - "path": self._obs_labels_path - } + if self._obs_labels_paths is not None and self._obs_labels_names is not None and len(self._obs_labels_paths) == len(self._obs_labels_names): + obs_labels = [] + for path, obs_type in zip(self._obs_labels_paths, self._obs_labels_names): + obs_labels.append({"path": path, "obsLabelsType": obs_type}) + options["obsLabels"] = obs_labels if len(options.keys()) > 0: obj_file_def = { "fileType": ft.ANNDATA_ZARR.value,