Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tkakar/cat 1081 kaggle 2 #106

Merged
merged 10 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
14 changes: 10 additions & 4 deletions src/portal_visualization/builder_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
SeqFISHViewConfBuilder,
IMSViewConfBuilder,
ImagePyramidViewConfBuilder,
SegImagePyramidViewConfBuilder,
EpicSegImagePyramidViewConfBuilder,
KaggleSegImagePyramidViewConfBuilder,
NanoDESIViewConfBuilder,
)
from .builders.anndata_builders import (
Expand All @@ -34,6 +35,7 @@ def process_hints(hints):
is_json = "json_based" in hints
is_spatial = "spatial" in hints
is_support = "is_support" in hints
is_seg_mask = "segmentation_mask" in hints

return (
is_image,
Expand All @@ -45,6 +47,7 @@ def process_hints(hints):
is_json,
is_spatial,
is_support,
is_seg_mask,
)


Expand All @@ -66,14 +69,17 @@ def get_view_config_builder(entity, get_entity, parent=None, epic_uuid=None):
is_anndata,
is_json,
is_spatial,
is_support
is_support,
is_seg_mask,
) = process_hints(hints)

# vis-lifted image pyramids
if parent is not None:
# TODO: For now epic (base image's) support datasets doesn't have any hints
if epic_uuid is not None:
return SegImagePyramidViewConfBuilder
if is_seg_mask and epic_uuid:
return EpicSegImagePyramidViewConfBuilder
elif is_seg_mask:
return KaggleSegImagePyramidViewConfBuilder

elif is_support and is_image:
ancestor_assaytype = get_entity(parent).get("soft_assaytype")
Expand Down
220 changes: 131 additions & 89 deletions src/portal_visualization/builders/imaging_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@
VitessceConfig,
MultiImageWrapper,
OmeTiffWrapper,
CoordinationLevel as CL,
get_initial_coordination_scope_prefix,
ObsSegmentationsOmeTiffWrapper,
ImageOmeTiffWrapper,
Component as cm,
)

from ..utils import get_matches, group_by_file_name, get_conf_cells
from ..utils import get_matches, group_by_file_name, get_conf_cells, get_found_images
from ..paths import (IMAGE_PYRAMID_DIR, OFFSETS_DIR, SEQFISH_HYB_CYCLE_REGEX,
SEQFISH_FILE_REGEX, SEGMENTATION_SUPPORT_IMAGE_SUBDIR,
SEGMENTATION_SUBDIR)
from .base_builders import ViewConfBuilder

BASE_IMAGE_VIEW_TYPE = 'image'
SEG_IMAGE_VIEW_TYPE = 'seg'
KAGGLE_IMAGE_VIEW_TYPE = 'kaggle-seg'


class AbstractImagingViewConfBuilder(ViewConfBuilder):
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
self.image_pyramid_regex = None
self.seg_image_pyramid_regex = None
self.use_full_resolution = []
self.use_physical_size_scaling = False
self.view_type = BASE_IMAGE_VIEW_TYPE
super().__init__(entity, groups_token, assets_endpoint, **kwargs)

def _get_img_and_offset_url(self, img_path, img_dir):
"""Create a url for the offsets and img.
:param str img_path: The path of the image
Expand Down Expand Up @@ -58,129 +73,155 @@ def _get_img_and_offset_url_seg(self, img_path, img_dir):

"""
img_url = self._build_assets_url(img_path)
offset_path = f'{SEGMENTATION_SUBDIR}/{OFFSETS_DIR}/{SEGMENTATION_SUPPORT_IMAGE_SUBDIR}'
offsets_path = re.sub(IMAGE_PYRAMID_DIR, OFFSETS_DIR, img_dir)
return (
img_url,
str(
re.sub(
r"ome\.tiff?",
"offsets.json",
re.sub(img_dir, offset_path, img_url),
re.sub(img_dir, offsets_path, img_url),
)
),
)

def _setup_view_config_raster(self, vc, dataset, disable_3d=[], use_full_resolution=[]):
vc.add_view(cm.SPATIAL, dataset=dataset, x=3, y=0, w=9, h=12).set_props(
useFullResolutionImage=use_full_resolution
)
vc.add_view(cm.DESCRIPTION, dataset=dataset, x=0, y=8, w=3, h=4)
vc.add_view(cm.LAYER_CONTROLLER, dataset=dataset, x=0, y=0, w=3, h=8).set_props(
disable3d=disable_3d, disableChannelsIfRgbDetected=True
)
return vc
def _add_segmentation_image(self, dataset):
file_paths_found = self._get_file_paths()
if self.seg_image_pyramid_regex is None:
raise ValueError("seg_image_pyramid_regex is not set. Cannot find segmentation images.")

def _setup_view_config_seg(self, vc, dataset, disable_3d=[], use_full_resolution=[]):
vc.add_view("spatialBeta", dataset=dataset, x=4, y=0, w=8, h=12).set_props(
useFullResolutionImage=use_full_resolution
)
vc.add_view("layerControllerBeta", dataset=dataset, x=0, y=0, w=4, h=8).set_props(
disable3d=disable_3d, disableChannelsIfRgbDetected=True
)
return vc
try:
found_images = get_found_images(self.seg_image_pyramid_regex, file_paths_found)
except Exception as e:
raise RuntimeError(f"Error while searching for segmentation images: {e}")

filtered_images = [img for img in found_images if SEGMENTATION_SUPPORT_IMAGE_SUBDIR not in img]

class ImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
"""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
self.use_full_resolution = []
self.use_physical_size_scaling = False
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
if not filtered_images:
raise FileNotFoundError(f"Segmentation assay with uuid {self._uuid} has no matching files")
tkakar marked this conversation as resolved.
Show resolved Hide resolved

def get_conf_cells(self, **kwargs):
file_paths_found = self._get_file_paths()
found_images = [
path for path in get_matches(
file_paths_found, self.image_pyramid_regex + r".*\.ome\.tiff?$",
img_url, offsets_url = self._get_img_and_offset_url(filtered_images[0], self.seg_image_pyramid_regex)
if dataset is not None:
dataset.add_object(
ObsSegmentationsOmeTiffWrapper(img_url=img_url, offsets_url=offsets_url,
obs_types_from_channel_names=True,
# coordinate_transformations=[{"type": "scale", "scale":
# [0.377.,0.377,1,1,1]}] # need to read from a file
)
)
if 'separate/' not in path # Exclude separate/* in MALDI-IMS
]

def _setup_view_config(self, vc, dataset, view_type, disable_3d=[], use_full_resolution=[]):
if view_type == BASE_IMAGE_VIEW_TYPE:
vc.add_view(cm.SPATIAL, dataset=dataset, x=3, y=0, w=9, h=12).set_props(
useFullResolutionImage=use_full_resolution
)
vc.add_view(cm.DESCRIPTION, dataset=dataset, x=0, y=8, w=3, h=4)
vc.add_view(cm.LAYER_CONTROLLER, dataset=dataset, x=0, y=0, w=3, h=8).set_props(
disable3d=disable_3d, disableChannelsIfRgbDetected=True
)
elif "seg" in view_type:
spatial_view = vc.add_view("spatialBeta", dataset=dataset, x=4, y=0, w=8, h=12).set_props(
useFullResolutionImage=use_full_resolution
)
lc_view = vc.add_view("layerControllerBeta", dataset=dataset, x=0, y=0, w=4, h=8).set_props(
disable3d=disable_3d, disableChannelsIfRgbDetected=True
)
# Adding the segmentation mask on top of the image
if view_type == KAGGLE_IMAGE_VIEW_TYPE:
# vc.link_views_by_dict([spatial_view, lc_view])
# TODO: The image-channel view disappears after the following
vc.link_views_by_dict([spatial_view, lc_view], {
'imageLayer': CL([{'photometricInterpretation': 'RGB', }]),
}, meta=True, scope_prefix=get_initial_coordination_scope_prefix("A", "image"))

return vc

def get_conf_cells_common(self, get_img_and_offset_url_func, **kwargs):
file_paths_found = self._get_file_paths()
found_images = get_found_images(self.image_pyramid_regex, file_paths_found)
found_images = sorted(found_images)
if len(found_images) == 0:
if len(found_images) == 0: # pragma: no cover
message = f"Image pyramid assay with uuid {self._uuid} has no matching files"
raise FileNotFoundError(message)

vc = VitessceConfig(name="HuBMAP Data Portal", schema_version=self._schema_version)
dataset = vc.add_dataset(name="Visualization Files")
images = []
for img_path in found_images:
img_url, offsets_url = self._get_img_and_offset_url(
img_path, self.image_pyramid_regex

if 'seg' in self.view_type:
img_url, offsets_url = get_img_and_offset_url_func(found_images[0], self.image_pyramid_regex)
dataset = dataset.add_object(
ImageOmeTiffWrapper(img_url=img_url, offsets_url=offsets_url, name=Path(found_images[0]).name)
)
images.append(
if self.view_type == KAGGLE_IMAGE_VIEW_TYPE:
self._add_segmentation_image(dataset)

else:
images = [
OmeTiffWrapper(
img_url=img_url, offsets_url=offsets_url, name=Path(img_path).name
)
for img_path in found_images
for img_url, offsets_url in [get_img_and_offset_url_func(img_path, self.image_pyramid_regex)]
]
dataset.add_object(
MultiImageWrapper(images, use_physical_size_scaling=self.use_physical_size_scaling)
)
dataset = dataset.add_object(
MultiImageWrapper(
images,
use_physical_size_scaling=self.use_physical_size_scaling
)
)
vc = self._setup_view_config_raster(
vc, dataset, use_full_resolution=self.use_full_resolution)
conf = vc.to_dict()
# Don't want to render all layers
del conf["datasets"][0]["files"][0]["options"]["renderLayers"]
conf = self._setup_view_config(
vc,
dataset,
self.view_type,
use_full_resolution=self.use_full_resolution).to_dict()
if self.view_type == BASE_IMAGE_VIEW_TYPE:
del conf["datasets"][0]["files"][0]["options"]["renderLayers"]
return get_conf_cells(conf)


class SegImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
"""Wrapper class for creating a standard view configuration for image pyramids for segmenation mask,
class ImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
"""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/
"""
self.image_pyramid_regex = f'{SEGMENTATION_SUBDIR}/{IMAGE_PYRAMID_DIR}/{SEGMENTATION_SUPPORT_IMAGE_SUBDIR}'
self.use_full_resolution = []
self.use_physical_size_scaling = False
https://portal.hubmapconsortium.org/browse/dataset/dc289471333309925e46ceb9bafafaf4
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
self.image_pyramid_regex = IMAGE_PYRAMID_DIR
self.view_type = BASE_IMAGE_VIEW_TYPE

def get_conf_cells(self, **kwargs):
file_paths_found = self._get_file_paths()
found_images = [
path for path in get_matches(
file_paths_found, self.image_pyramid_regex + r".*\.ome\.tiff?$",
)
if 'separate/' not in path # Exclude separate/* in MALDI-IMS
]
found_images = sorted(found_images)
if len(found_images) == 0: # pragma: no cover
message = f"Image pyramid assay with uuid {self._uuid} has no matching files"
raise FileNotFoundError(message)
return self.get_conf_cells_common(self._get_img_and_offset_url, **kwargs)

vc = VitessceConfig(name="HuBMAP Data Portal", schema_version=self._schema_version)
dataset = vc.add_dataset(name="Visualization Files")
# The base-image will always be 1
if len(found_images) == 1:
img_url, offsets_url = self._get_img_and_offset_url_seg(
found_images[0], self.image_pyramid_regex
)

image = ImageOmeTiffWrapper(
img_url=img_url, offsets_url=offsets_url, name=Path(found_images[0]).name
)
class EpicSegImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
"""Wrapper class for creating a standard view configuration for image pyramids for EPIC segmentation mask,
i.e for high resolution viz-lifted imaging datasets like
https://portal.dev.hubmapconsortium.org/browse/dataset/df7cac7cb67a822f7007b57c4d8f5e7d
"""

dataset = dataset.add_object(image)
vc = self._setup_view_config_seg(
vc, dataset, use_full_resolution=self.use_full_resolution)
conf = vc.to_dict()
return get_conf_cells(conf)
def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
self.image_pyramid_regex = f"{SEGMENTATION_SUBDIR}/{IMAGE_PYRAMID_DIR}/{SEGMENTATION_SUPPORT_IMAGE_SUBDIR}"
self.view_type = SEG_IMAGE_VIEW_TYPE

def get_conf_cells(self, **kwargs):
return self.get_conf_cells_common(self._get_img_and_offset_url_seg, **kwargs)


class KaggleSegImagePyramidViewConfBuilder(AbstractImagingViewConfBuilder):
tkakar marked this conversation as resolved.
Show resolved Hide resolved
"""Wrapper class for creating a standard view configuration for image pyramids for kaggle-2 datasets, that show,
segmentation mask layered over a base image-pyramid, however, the file structure is different than
EPIC segmentation masks (EpicSegImagePyramidViewConfBuilder)
i.e for high resolution viz-lifted imaging datasets like
https://portal.dev.hubmapconsortium.org/browse/dataset/534a590d7336aa99c7fc7afd41e995fc
"""

def __init__(self, entity, groups_token, assets_endpoint, **kwargs):
super().__init__(entity, groups_token, assets_endpoint, **kwargs)
self.image_pyramid_regex = f"{IMAGE_PYRAMID_DIR}/{SEGMENTATION_SUPPORT_IMAGE_SUBDIR}"
self.seg_image_pyramid_regex = IMAGE_PYRAMID_DIR
self.view_type = KAGGLE_IMAGE_VIEW_TYPE

def get_conf_cells(self, **kwargs):
return self.get_conf_cells_common(self._get_img_and_offset_url_seg, **kwargs)


class IMSViewConfBuilder(ImagePyramidViewConfBuilder):
Expand Down Expand Up @@ -248,9 +289,10 @@ def get_conf_cells(self, **kwargs):
)
)
dataset = dataset.add_object(MultiImageWrapper(image_wrappers))
vc = self._setup_view_config_raster(
vc = self._setup_view_config(
vc,
dataset,
self.view_type,
disable_3d=[self._get_hybcycle(img_path) for img_path in sorted_images]
)
conf = vc.to_dict()
Expand Down
2 changes: 1 addition & 1 deletion src/portal_visualization/builders/sprm_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def get_conf_cells(self, **kwargs):
if self._files[0]["rel_path"] not in file_paths_found:
# This tile has no segmentations,
# so only show Spatial component without cells sets, genes etc.
vc = self._setup_view_config_raster(vc, dataset, disable_3d=[self._image_name])
vc = self._setup_view_config(vc, dataset, self.view_type, disable_3d=[self._image_name])
else:
# This tile has segmentations so show the analysis results.
for file in self._files:
Expand Down
2 changes: 1 addition & 1 deletion src/portal_visualization/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def get_vitessce_conf_cells_and_lifted_uuid(
else: # pragma: no cover # We have separate tests for the builder logic
try:
def get_entity(entity):
if (type(entity) is str):
if (isinstance(entity, str)):
return self.get_entity(uuid=entity)
return self.get_entity(uuid=entity.get('uuid'))
Builder = get_view_config_builder(entity, get_entity, parent, epic_uuid)
Expand Down
10 changes: 10 additions & 0 deletions src/portal_visualization/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ def _get_cells_from_obj(vc_obj):
]


def get_found_images(image_pyramid_regex, file_paths_found):
found_images = [
path for path in get_matches(
file_paths_found, image_pyramid_regex + r".*\.ome\.tiff?$",
)
if 'separate/' not in path
]
return found_images


def files_from_response(response_json):
'''
>>> response_json = {'hits': {'hits': [
Expand Down
Loading