diff --git a/src/portal_visualization/builder_factory.py b/src/portal_visualization/builder_factory.py index 3b0b3a2..23a08d6 100644 --- a/src/portal_visualization/builder_factory.py +++ b/src/portal_visualization/builder_factory.py @@ -26,7 +26,7 @@ # { # "assaytype": "image_pyramid", # "description": "Image Pyramid", -# "vitessce_hints": [ +# "vitessce-hints": [ # "is_image", # "pyramid" # ] @@ -34,13 +34,20 @@ def get_ancestor_assaytypes(entity, get_assaytype): - return [get_assaytype(ancestor.get('uuid')).get('assaytype') + return [get_assaytype(ancestor).get('assaytype') for ancestor in entity.get('immediate_ancestors')] +# +# This function is the main entrypoint for the builder factory. +# It returns the correct builder for the given entity. +# +# The entity is a dict that contains the entity UUID and metadata. +# `get_assaytype` is a function which takes an entity UUID and returns +# the assaytype and vitessce-hints for that entity. def get_view_config_builder(entity, get_assaytype): - assay = get_assaytype(entity.get('uuid')) + assay = get_assaytype(entity) assay_name = assay.get('assaytype') hints = assay.get('vitessce-hints', []) dag_provenance_list = entity.get('metadata', {}).get('dag_provenance_list', []) @@ -48,17 +55,26 @@ def get_view_config_builder(entity, get_assaytype): for dag in dag_provenance_list if 'name' in dag] if "is_image" in hints: if 'sprm' in hints and 'anndata' in hints: + # e.g. CellDIVE [DeepCell + SPRM] + # sample entity: c3be5650e93907b68ddbdb22b948db32 return MultiImageSPRMAnndataViewConfBuilder if "codex" in hints: if ('sprm-to-anndata.cwl' in dag_names): + # e.g. 'CODEX [Cytokit + SPRM] + # sample entity: 43213991a54ce196d406707ffe2e86bd return StitchedCytokitSPRMViewConfBuilder + # Cannot find an example of this in the wild, every CODEX entity has the + # sprm-to-anndata.cwl in its dag_provenance_list return TiledSPRMViewConfBuilder # Check types of image pyramid ancestors to determine which builder to use ancestor_assaytypes = get_ancestor_assaytypes(entity, get_assaytype) + # e.g. c6a254b2dc2ed46b002500ade163a7cc if SEQFISH in [assaytype for assaytype in ancestor_assaytypes]: return SeqFISHViewConfBuilder + # e.g. 3bc3ad124014a632d558255626bf38c9 if MALDI_IMS in [assaytype for assaytype in ancestor_assaytypes]: return IMSViewConfBuilder + # e.g. 6b93107731199733f266bbd0f3bc9747 if NANODESI in [assaytype for assaytype in ancestor_assaytypes]: return NanoDESIViewConfBuilder return ImagePyramidViewConfBuilder @@ -66,11 +82,16 @@ def get_view_config_builder(entity, get_assaytype): # This is the zarr-backed anndata pipeline. if "anndata-to-ui.cwl" in dag_names: if assay_name == SALMON_RNASSEQ_SLIDE: + # e.g. 2a590db3d7ab1e1512816b165d95cdcf return SpatialRNASeqAnnDataZarrViewConfBuilder + # e.g. e65175561b4b17da5352e3837aa0e497 return RNASeqAnnDataZarrViewConfBuilder + # e.g. c019a1cd35aab4d2b4a6ff221e92aaab return RNASeqViewConfBuilder if "atac" in hints: + # e.g. d4493657cde29702c5ed73932da5317c return ATACSeqViewConfBuilder + # any entity with no hints, e.g. 2c2179ea741d3bbb47772172a316a2bf return NullViewConfBuilder diff --git a/test/test_builders.py b/test/test_builders.py index 31d154a..b065b29 100644 --- a/test/test_builders.py +++ b/test/test_builders.py @@ -10,8 +10,6 @@ import pytest import zarr -from hubmap_commons.type_client import TypeClient - from src.portal_visualization.builder_factory import get_view_config_builder, has_visualization @@ -38,18 +36,10 @@ class MockResponse: defaults = json.load((Path(__file__).parent.parent / 'src/defaults.json').open()) - -def get_assay(name): - # This code could also be used in portal-ui. - # search-api might skip the REST interface. - type_client = TypeClient(defaults['types_url']) - return type_client.getAssayType(name) - - + def get_assaytype(uuid): requests.get(f'{defaults["assaytypes_url"]}/{uuid}').json() - @pytest.mark.parametrize( "has_vis_entity", [ @@ -61,7 +51,7 @@ def get_assaytype(uuid): ids=lambda has_vis_entity: f'has_visualization={has_vis_entity[0]}') def test_has_visualization(has_vis_entity): has_vis, entity = has_vis_entity - assert has_vis == has_visualization(entity, get_assay) + assert has_vis == has_visualization(entity, get_assaytype) def mock_zarr_store(entity_path, mocker): @@ -97,7 +87,7 @@ def test_entity_to_vitessce_conf(entity_path, mocker): else None) entity = json.loads(entity_path.read_text()) - Builder = get_view_config_builder(entity, get_assay) + Builder = get_view_config_builder(entity, get_assaytype) assert Builder.__name__ == entity_path.parent.name # Envvars should not be set during normal test runs, @@ -130,7 +120,7 @@ def test_entity_to_error(entity_path, mocker): entity = json.loads(entity_path.read_text()) with pytest.raises(Exception) as error_info: - Builder = get_view_config_builder(entity, get_assay) + Builder = get_view_config_builder(entity, get_assaytype) builder = Builder(entity, 'groups_token', 'https://example.com/') builder.get_conf_cells() actual_error = f'{error_info.type.__name__}: {error_info.value.args[0]}' @@ -157,7 +147,7 @@ def clean_cells(cells): args = parser.parse_args() entity = json.loads(args.input.read_text()) - Builder = get_view_config_builder(entity, get_assay) + Builder = get_view_config_builder(entity, get_assaytype) builder = Builder(entity, 'groups_token', 'https://example.com/') conf, cells = builder.get_conf_cells()