Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $ pip install .
$ src/vis-preview.py --help
usage: vis-preview.py [-h] (--url URL | --json JSON) [--types_url URL]
[--assets_url URL] [--token TOKEN] [--marker MARKER]
[--to_json]
[--to_json] [--conf_index I]

Given HuBMAP Dataset JSON, generate a Vitessce viewconf, and load vitessce.io.

Expand All @@ -31,6 +31,9 @@ optional arguments:
--marker MARKER Marker to highlight in visualization; Only used in some
visualizations.
--to_json Output viewconf, rather than open in browser.
--conf_index I Old untiled imagery produces multiple viewconfs, one for
each tile. This allows you display a viewconf other than
the first.
```

## Background
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.6
0.1.0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change to the interface, so incrementing the minor version number seems appropriate.

13 changes: 6 additions & 7 deletions src/portal_visualization/builders/anndata_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import zarr

from .base_builders import ViewConfBuilder
from ..utils import get_conf_cells


class RNASeqAnnDataZarrViewConfBuilder(ViewConfBuilder):
Expand All @@ -18,14 +17,14 @@ class RNASeqAnnDataZarrViewConfBuilder(ViewConfBuilder):
https://portal.hubmapconsortium.org/browse/dataset/e65175561b4b17da5352e3837aa0e497
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
def __init__(self, entity, groups_token, assets_endpoint):
super().__init__(entity, groups_token, assets_endpoint)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The **kwargs has masked bugs: We thought we were using the right kwarg, but there was a typo, so the code assumed no value was supplied and it continued on without complaint.

# Spatially resolved RNA-seq assays require some special handling,
# and others do not.
self._is_spatial = False
self._scatterplot_w = 9

def get_conf_cells(self, marker=None):
def get_configs(self, marker=None):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Builder subclasses now define get_configs instead of get_conf_cells, which is now defined only on the base class. The idea is to factor up behavior which is shared between subclasses.

zarr_path = 'hubmap_ui/anndata-zarr/secondary_analysis.zarr'
file_paths_found = [file["rel_path"] for file in self._entity["files"]]
# Use .zgroup file as proxy for whether or not the zarr store is present.
Expand Down Expand Up @@ -77,7 +76,7 @@ def get_conf_cells(self, marker=None):
))

vc = self._setup_anndata_view_config(vc, dataset, marker)
return get_conf_cells(vc)
return [vc]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • See above: This method is now only responsible for returning confs
  • Big change: Every class now returns a list of viewconfs. For most, it's only one element, but consistency in return types is a good thing.


def _setup_anndata_view_config(self, vc, dataset, marker=None):
scatterplot = vc.add_view(
Expand Down Expand Up @@ -118,8 +117,8 @@ class SpatialRNASeqAnnDataZarrViewConfBuilder(RNASeqAnnDataZarrViewConfBuilder):
https://portal.hubmapconsortium.org/browse/dataset/2a590db3d7ab1e1512816b165d95cdcf
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
def __init__(self, entity, groups_token, assets_endpoint):
super().__init__(entity, groups_token, assets_endpoint)
# Spatially resolved RNA-seq assays require some special handling,
# and others do not.
self._is_spatial = True
Expand Down
41 changes: 33 additions & 8 deletions src/portal_visualization/builders/base_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,40 @@
from collections import namedtuple
from abc import ABC, abstractmethod

import nbformat

ConfCells = namedtuple('ConfCells', ['conf', 'cells'])

ConfigsCells = namedtuple('ConfigsCells', ['configs', 'cells'])


def _get_cells_from_conf_list(confs):
cells = []
if len(confs) > 1:
cells.append(nbformat.v4.new_markdown_cell('Multiple visualizations are available.'))
for conf in confs:
cells.extend(_get_cells_from_conf(conf))
return cells


def _get_cells_from_conf(conf):
imports, conf_expression = conf.to_python()
return [
nbformat.v4.new_code_cell(f'from vitessce import {", ".join(imports)}'),
nbformat.v4.new_code_cell(f'conf = {conf_expression}\nconf.widget()'),
]
Copy link
Contributor Author

@mccalluc mccalluc Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these are moved from utils, since this is the only code that needs them.



class NullViewConfBuilder():
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
def __init__(self, entity, groups_token, assets_endpoint):
# Just so it has the same signature as the other builders
pass

def get_conf_cells(self, **kwargs):
return ConfCells(None, None)
def get_configs_cells(self, marker=None):
return ConfigsCells([], [])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, even for the Null case we return lists. (One possibility for the cells would be to generate a message that there is no visualization... Sort of depends on the direction taken by workspaces.)



class ViewConfBuilder(ABC):
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
def __init__(self, entity, groups_token, assets_endpoint):
"""Object for building the vitessce configuration.
:param dict entity: Entity response from search index (from the entity API)
:param str groups_token: Groups token for use in authenticating API
Expand All @@ -29,9 +48,15 @@ def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
self._files = []

@abstractmethod
def get_conf_cells(self, **kwargs): # pragma: no cover
def get_configs(self): # pragma: no cover
raise NotImplementedError()

def get_configs_cells(self, marker=None):
kwargs = {'marker': marker} if marker is not None else {}
configs = self.get_configs(**kwargs)
cells = _get_cells_from_conf_list(configs)
return ConfigsCells(configs, cells)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new base class method that constructs the list of cells and returns a tuple.


def _replace_url_in_file(self, file):
"""Replace url in incoming file object
:param dict file: File dict which will have its rel_path replaced by url
Expand Down Expand Up @@ -118,5 +143,5 @@ def _get_file_paths(self):
class _DocTestBuilder(ViewConfBuilder): # pragma: no cover
# The doctests on the methods in this file need a concrete class to instantiate:
# We need a concrete definition for this method, even if it's never used.
def get_conf_cells(self, **kwargs):
pass
def get_configs(self):
raise NotImplementedError()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be called... if it were, we want an error.

26 changes: 12 additions & 14 deletions src/portal_visualization/builders/imaging_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Component as cm,
)

from ..utils import get_matches, group_by_file_name, get_conf_cells
from ..utils import get_matches, group_by_file_name
from ..paths import IMAGE_PYRAMID_DIR, OFFSETS_DIR, SEQFISH_HYB_CYCLE_REGEX, SEQFISH_FILE_REGEX
from .base_builders import ViewConfBuilder

Expand All @@ -23,7 +23,7 @@ def _get_img_and_offset_url(self, img_path, img_dir):

>>> from pprint import pprint
>>> class ConcreteBuilder(AbstractImagingViewConfBuilder):
... def get_conf_cells(self, **kwargs):
... def get_configs(self):
... pass
>>> builder = ConcreteBuilder(
... entity={ "uuid": "uuid" },
Expand Down Expand Up @@ -56,15 +56,15 @@ def _setup_view_config_raster(self, vc, dataset, disable_3d=[]):


class ImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
def __init__(self, entity, groups_token, assets_endpoint):
"""Wrapper class for creating a standard view configuration for image pyramids,
i.e for high resolution viz-lifted imaging datasets like
https://portal.hubmapconsortium.org/browse/dataset/dc289471333309925e46ceb9bafafaf4
"""
self.image_pyramid_regex = IMAGE_PYRAMID_DIR
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
super().__init__(entity, groups_token, assets_endpoint)

def get_conf_cells(self, **kwargs):
def get_configs(self):
file_paths_found = self._get_file_paths()
found_images = [
path for path in get_matches(
Expand Down Expand Up @@ -93,7 +93,7 @@ def get_conf_cells(self, **kwargs):
conf = vc.to_dict()
# Don't want to render all layers
del conf["datasets"][0]["files"][0]["options"]["renderLayers"]
return get_conf_cells(conf)
return [VitessceConfig.from_dict(conf)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another change for the sake of consistency: get_configs will now always return a list of configuration objects, instead of returning dicts for some classes, objects for others, and lists in a couple cases.



class IMSViewConfBuilder(ImagePyramidViewConfBuilder):
Expand All @@ -102,8 +102,8 @@ class IMSViewConfBuilder(ImagePyramidViewConfBuilder):
of all the channels separated out.
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
def __init__(self, entity, groups_token, assets_endpoint):
super().__init__(entity, groups_token, assets_endpoint)
# Do not show the separated mass-spec images.
self.image_pyramid_regex = (
re.escape(IMAGE_PYRAMID_DIR) + r"(?!/ometiffs/separate/)"
Expand All @@ -116,7 +116,7 @@ class SeqFISHViewConfBuilder(AbstractImagingViewConfBuilder):
grouped together per position in a single Vitessce configuration.
"""

def get_conf_cells(self, **kwargs):
def get_configs(self):
file_paths_found = [file["rel_path"] for file in self._entity["files"]]
full_seqfish_regex = "/".join(
[
Expand Down Expand Up @@ -159,13 +159,11 @@ def get_conf_cells(self, **kwargs):
conf = vc.to_dict()
# Don't want to render all layers
del conf["datasets"][0]["files"][0]["options"]["renderLayers"]
confs.append(conf)
return get_conf_cells(confs)
confs.append(VitessceConfig.from_dict(conf))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: We're always objects now.

return confs

def _get_hybcycle(self, image_path):
return re.search(SEQFISH_HYB_CYCLE_REGEX, image_path)[0]

def _get_pos_name(self, image_path):
return re.search(SEQFISH_FILE_REGEX, image_path)[0].split(".")[
0
]
return re.search(SEQFISH_FILE_REGEX, image_path)[0].split(".")[0]
13 changes: 6 additions & 7 deletions src/portal_visualization/builders/scatterplot_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
)


from ..utils import get_conf_cells
from ..paths import SCRNA_SEQ_DIR, SCATAC_SEQ_DIR
from .base_builders import ViewConfBuilder

Expand All @@ -18,7 +17,7 @@ class AbstractScatterplotViewConfBuilder(ViewConfBuilder):
from h5ad-to-arrow.cwl.
"""

def get_conf_cells(self, **kwargs):
def get_configs(self):
file_paths_expected = [file["rel_path"] for file in self._files]
file_paths_found = self._get_file_paths()
# We need to check that the files we expect actually exist.
Expand All @@ -33,7 +32,7 @@ def get_conf_cells(self, **kwargs):
for file in self._files:
dataset = dataset.add_file(**(self._replace_url_in_file(file)))
vc = self._setup_scatterplot_view_config(vc, dataset)
return get_conf_cells(vc)
return [vc]

def _setup_scatterplot_view_config(self, vc, dataset):
vc.add_view(cm.SCATTERPLOT, dataset=dataset, mapping="UMAP", x=0, y=0, w=9, h=12)
Expand All @@ -47,8 +46,8 @@ class RNASeqViewConfBuilder(AbstractScatterplotViewConfBuilder):
from h5ad-to-arrow.cwl (August 2020 release).
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
def __init__(self, entity, groups_token, assets_endpoint):
super().__init__(entity, groups_token, assets_endpoint)
# All "file" Vitessce objects that do not have wrappers.
self._files = [
{
Expand All @@ -70,8 +69,8 @@ class ATACSeqViewConfBuilder(AbstractScatterplotViewConfBuilder):
from h5ad-to-arrow.cwl.
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
def __init__(self, entity, groups_token, assets_endpoint):
super().__init__(entity, groups_token, assets_endpoint)
# All "file" Vitessce objects that do not have wrappers.
self._files = [
{
Expand Down
Loading