diff --git a/omeroweb/webclient/views.py b/omeroweb/webclient/views.py index 2435222042..246d6aff99 100755 --- a/omeroweb/webclient/views.py +++ b/omeroweb/webclient/views.py @@ -44,6 +44,7 @@ from omeroweb.version import omeroweb_buildyear as build_year from omeroweb.version import omeroweb_version as omero_version +from omeroweb.webgateway.util import get_rendering_def import omero import omero.scripts @@ -1773,7 +1774,8 @@ def load_metadata_preview(request, c_type, c_id, conn=None, share_id=None, **kwa allRdefs = manager.image.getAllRenderingDefs() rdefs = {} - rdefId = manager.image.getRenderingDefId() + rdef = get_rendering_def(manager.image) + rdefId = rdef["id"] if rdef is not None else None # remove duplicates per user for r in allRdefs: ownerId = r["owner"]["id"] diff --git a/omeroweb/webgateway/marshal.py b/omeroweb/webgateway/marshal.py index 80008ea3c2..a9eea668a9 100644 --- a/omeroweb/webgateway/marshal.py +++ b/omeroweb/webgateway/marshal.py @@ -22,12 +22,20 @@ import time import re import logging -import traceback from future.utils import isbytes, bytes_to_native_str +from datetime import datetime +from omero.model.enums import PixelsTypeint8, PixelsTypeuint8, PixelsTypeint16 +from omero.model.enums import PixelsTypeuint16, PixelsTypeint32 +from omero.model.enums import PixelsTypeuint32, PixelsTypefloat +from omero.model.enums import PixelsTypedouble + +from omero.gateway import ChannelWrapper from omero.rtypes import unwrap from omero_marshal import get_encoder +from .util import get_rendering_def, load_re + logger = logging.getLogger(__name__) # OMERO.insight point list regular expression @@ -37,6 +45,32 @@ OME_MODEL_POINT_LIST_RE = re.compile(r"([\d.]+),([\d.]+)") +def getChannelsNoRe(image): + """ + Returns a list of Channels. + + This differs from image.getChannels(noRE=True) in that we load statsInfo + here which is used by channel.getWindowMin() and channel.getWindowMax() + + :return: Channels + :rtype: List of :class:`ChannelWrapper` + """ + + conn = image._conn + pid = image.getPixelsId() + params = omero.sys.ParametersI() + params.addId(pid) + query = """select p from Pixels p join fetch p.channels as c + join fetch c.logicalChannel as lc + left outer join fetch c.statsInfo + where p.id=:id""" + pixels = conn.getQueryService().findByQuery(query, params, conn.SERVICE_OPTS) + return [ + ChannelWrapper(conn, c, idx=n, img=image) + for n, c in enumerate(pixels.iterateChannels()) + ] + + def eventContextMarshal(event_context): """ Marshals the omero::sys::EventContext as a dict. @@ -73,6 +107,77 @@ def eventContextMarshal(event_context): return ctx +def getPixelRange(image): + minVals = { + PixelsTypeint8: -128, + PixelsTypeuint8: 0, + PixelsTypeint16: -32768, + PixelsTypeuint16: 0, + PixelsTypeint32: -2147483648, + PixelsTypeuint32: 0, + PixelsTypefloat: -2147483648, + PixelsTypedouble: -2147483648, + } + maxVals = { + PixelsTypeint8: 127, + PixelsTypeuint8: 255, + PixelsTypeint16: 32767, + PixelsTypeuint16: 65535, + PixelsTypeint32: 2147483647, + PixelsTypeuint32: 4294967295, + PixelsTypefloat: 2147483647, + PixelsTypedouble: 2147483647, + } + pixtype = image.getPrimaryPixels().getPixelsType().getValue() + return [minVals[pixtype], maxVals[pixtype]] + + +def getWindowMin(channel): + si = channel._obj.getStatsInfo() + if si is None: + return None + return si.getGlobalMin().val + + +def getWindowMax(channel): + si = channel._obj.getStatsInfo() + if si is None: + return None + return si.getGlobalMax().val + + +def rdefMarshal(rdef, image, pixel_range): + + channels = [] + + for rdef_ch, channel in zip(rdef["c"], getChannelsNoRe(image)): + + chan = { + "emissionWave": channel.getEmissionWave(), + "label": channel.getLabel(), + "color": rdef_ch["color"], + # 'reverseIntensity' is deprecated. Use 'inverted' + "inverted": rdef_ch["inverted"], + "reverseIntensity": rdef_ch["inverted"], + "family": rdef_ch["family"], + "coefficient": rdef_ch["coefficient"], + "active": rdef_ch["active"], + } + + chan["window"] = { + "min": getWindowMin(channel) or pixel_range[0], + "max": getWindowMax(channel) or pixel_range[1], + "start": rdef_ch["start"], + "end": rdef_ch["end"], + } + + lut = channel.getLut() + if lut and len(lut) > 0: + chan["lut"] = lut + channels.append(chan) + return channels + + def channelMarshal(channel): """ return a dict with all there is to know about a channel @@ -114,6 +219,7 @@ def imageMarshal(image, key=None, request=None): @return: Dict """ + # projection / invertAxis etc from annotation image.loadRenderOptions() pr = image.getProject() ds = None @@ -165,31 +271,6 @@ def imageMarshal(image, key=None, request=None): "canLink": image.canLink(), }, } - try: - reOK = image._prepareRenderingEngine() - if not reOK: - logger.debug("Failed to prepare Rendering Engine for imageMarshal") - return rv - except omero.ConcurrencyException as ce: - backOff = ce.backOff - rv = {"ConcurrencyException": {"backOff": backOff}} - return rv - except Exception as ex: # Handle everything else. - rv["Exception"] = ex.message - logger.error(traceback.format_exc()) - return rv # Return what we have already, in case it's useful - - # big images - levels = image._re.getResolutionLevels() - tiles = levels > 1 - rv["tiles"] = tiles - if tiles: - width, height = image._re.getTileSize() - zoomLevelScaling = image.getZoomLevelScaling() - - rv.update({"tile_size": {"width": width, "height": height}, "levels": levels}) - if zoomLevelScaling is not None: - rv["zoomLevelScaling"] = zoomLevelScaling nominalMagnification = ( image.getObjectiveSettings() is not None @@ -197,14 +278,14 @@ def imageMarshal(image, key=None, request=None): or None ) + if nominalMagnification is not None: + rv.update({"nominalMagnification": nominalMagnification}) + try: server_settings = request.session.get("server_settings", {}).get("viewer", {}) except Exception: server_settings = {} init_zoom = server_settings.get("initial_zoom_level", 0) - if init_zoom < 0: - init_zoom = levels + init_zoom - interpolate = server_settings.get("interpolate_pixels", True) try: @@ -236,19 +317,20 @@ def pixel_size_in_microns(method): }, } ) - if init_zoom is not None: - rv["init_zoom"] = init_zoom - if nominalMagnification is not None: - rv.update({"nominalMagnification": nominalMagnification}) + + rdef = get_rendering_def(image, rv) + if not rdef: + return rv + try: - rv["pixel_range"] = image.getPixelRange() - rv["channels"] = [channelMarshal(x) for x in image.getChannels()] + rv["pixel_range"] = getPixelRange(image) + rv["channels"] = rdefMarshal(rdef, image, rv["pixel_range"]) rv["split_channel"] = image.splitChannelDims() rv["rdefs"] = { - "model": (image.isGreyscaleRenderingModel() and "greyscale" or "color"), + "model": (rdef["model"] == "greyscale" and "greyscale" or "color"), "projection": image.getProjection(), - "defaultZ": image._re.getDefaultZ(), - "defaultT": image._re.getDefaultT(), + "defaultZ": rdef["z"], + "defaultT": rdef["t"], "invertAxis": image.isInvertedAxis(), } except TypeError: @@ -264,9 +346,45 @@ def pixel_size_in_microns(method): "defaultT": 0, "invertAxis": image.isInvertedAxis(), } + except AttributeError: + # Why do we do raise just for this exception?! rv = None raise + + # If image is big - need to load RE to get resolution levels + # NB: some small images like OME-Zarr can have pyramids + # but these will be ignored + + # TEMP - for A/B/C testing based on image ID + starttime = datetime.now() + big_image = (rv["size"]["width"] * rv["size"]["height"]) > (3000 * 3000) + rv["tile_ABC"] = image.id % 3 + if image.id % 3 == 0 and not big_image: + rv["tiles"] = False + rv["tiles_test"] = "No" + elif image.id % 3 == 1 and not image.requiresPixelsPyramid(): + rv["tiles"] = False + rv["tiles_test"] = "requiresPixelsPyramid" + else: + rv["tiles_test"] = "re.getResolutionLevels" + if load_re(image, rv): + levels = image._re.getResolutionLevels() + tiles = levels > 1 + rv["tiles"] = tiles + if tiles: + width, height = image._re.getTileSize() + zoomLevelScaling = image.getZoomLevelScaling() + rv.update( + {"tile_size": {"width": width, "height": height}, "levels": levels} + ) + if zoomLevelScaling is not None: + rv["zoomLevelScaling"] = zoomLevelScaling + if init_zoom < 0: + init_zoom = levels + init_zoom + rv["init_zoom"] = init_zoom + rv["tiles_time"] = str(datetime.now() - starttime) + if key is not None and rv is not None: for k in key.split("."): rv = rv.get(k, {}) diff --git a/omeroweb/webgateway/util.py b/omeroweb/webgateway/util.py index 7a3a2468c6..291b89be58 100644 --- a/omeroweb/webgateway/util.py +++ b/omeroweb/webgateway/util.py @@ -23,6 +23,9 @@ import shutil import logging +import omero +import traceback + try: import long except ImportError: @@ -238,3 +241,38 @@ def points_string_to_XY_list(string): x, y = xy.split(",") xyList.append((float(x.strip()), float(y.strip()))) return xyList + + +def load_re(image, rsp_dict=None): + rsp_dict = {} if rsp_dict is None else rsp_dict + reOK = False + try: + reOK = image._prepareRenderingEngine() + if not reOK: + rsp_dict["Error"] = "Failed to prepare Rendering Engine for imageMarshal" + logger.debug("Failed to prepare Rendering Engine for imageMarshal") + except omero.ConcurrencyException as ce: + backOff = ce.backOff + rsp_dict["ConcurrencyException"] = {"backOff": backOff} + except Exception as ex: # Handle everything else. + rsp_dict["Exception"] = ex.message + logger.error(traceback.format_exc()) + return reOK + + +def get_rendering_def(image, rsp_dict=None): + # rsp_dict allows errors to be added + # Try to get user's rendering def + exp_id = image._conn.getUserId() + rdefs = image.getAllRenderingDefs(exp_id) + if len(rdefs) == 0: + # try to create our own rendering settings + if load_re(image, rsp_dict): + rdefs = image.getAllRenderingDefs(exp_id) + # otherwise use owners + if len(rdefs) == 0: + owner_id = image.getDetails().getOwner().id + if owner_id != exp_id: + rdefs = image.getAllRenderingDefs(owner_id) + + return rdefs[0] if len(rdefs) > 0 else None