From 11c0079de118f0eab60b36751210ad6064528390 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 2 May 2024 16:53:09 +0100 Subject: [PATCH] Show warning if user exceeds Z-projection limit --- omero_figure/urls.py | 5 ++ omero_figure/views.py | 49 +++++++++++++++++++ src/css/figure.css | 5 -- src/index.html | 1 + src/js/models/panel_model.js | 30 +++++++++--- src/js/views/right_panel_view.js | 5 ++ src/js/views/scalebar_form_view.js | 3 +- .../image_display_options.template.html | 15 +++--- 8 files changed, 95 insertions(+), 18 deletions(-) diff --git a/omero_figure/urls.py b/omero_figure/urls.py index 11fb09fc3..9999d3d4a 100644 --- a/omero_figure/urls.py +++ b/omero_figure/urls.py @@ -33,6 +33,11 @@ re_path(r'^imgData/(?P[0-9]+)/$', views.img_data_json, name='figure_imgData'), + re_path(r'^max_projection_range_exceeded/' + r'(?P[0-9]+)/(?:(?P[0-9]+)/)?(?:(?P[0-9]+)/)?$', + views.max_projection_range_exceeded, + name='max_projection_range_exceeded'), + # Send json to OMERO to create pdf using scripting service re_path(r'^make_web_figure/', views.make_web_figure, name='make_web_figure'), diff --git a/omero_figure/views.py b/omero_figure/views.py index 2108239d9..2a7e89cb6 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -91,6 +91,8 @@ def index(request, file_id=None, conn=None, **kwargs): max_w, max_h = conn.getMaxPlaneSize() max_plane_size = max_w * max_h length_units = get_length_units() + cfg = conn.getConfigService() + max_bytes = cfg.getConfigValue('omero.pixeldata.max_projection_bytes') is_public_user = "false" if (hasattr(settings, 'PUBLIC_USER') and settings.PUBLIC_USER == user.getOmeName()): @@ -117,6 +119,9 @@ def index(request, file_id=None, conn=None, **kwargs): 'const MAX_PLANE_SIZE = %s;' % max_plane_size) html = html.replace('const LENGTH_UNITS = LENGTHUNITS;', 'const LENGTH_UNITS = %s;' % json.dumps(length_units)) + if max_bytes: + html = html.replace('const MAX_PROJECTION_BYTES = 1024 * 1024 * 256;', + 'const MAX_PROJECTION_BYTES = %s;' % max_bytes) if export_enabled: html = html.replace('const EXPORT_ENABLED = false;', 'const EXPORT_ENABLED = true;') @@ -136,6 +141,50 @@ def index(request, file_id=None, conn=None, **kwargs): return HttpResponse(html) +@login_required() +def max_projection_range_exceeded(request, iid, z=None, t=None, + conn=None, **kwargs): + """ + The app will use this URL instead of `render_image/` if the + requested Z-projection range exceeds the maximum projected + bytes (given the number of active channels) + + This returns a placeholder image with suitable message + """ + + from PIL import Image, ImageDraw, ImageFont + + font14 = ImageFont.load_default(14) + font20 = ImageFont.load_default(20) + msg = "Max Z projection exceeded" + msg_size = font20.getbbox(msg) + txt_w = msg_size[2] + txt_h = msg_size[3] + advice = "Try to turn off channels or reduce Z-range" + advice_size = font14.getbbox(advice) + adv_w = advice_size[2] + adv_h = advice_size[3] + + image_size = max(txt_w, adv_w) + 10 + line_space = 10 + total_h = txt_h + adv_h + line_space + + im = Image.new("RGB", (image_size, image_size), (5, 0, 0)) + draw = ImageDraw.Draw(im) + text_y = im.size[1]/2 - total_h/2 + draw.text((im.size[0]/2 - txt_w/2, text_y), msg, + font=font20, + fill=(256, 256, 256)) + text_y += txt_h + line_space + draw.text((im.size[0]/2 - adv_w/2, text_y), advice, + font=font14, + fill=(200, 200, 200)) + + rv = BytesIO() + im.save(rv, "jpeg", quality=90) + return HttpResponse(rv.getvalue(), content_type="image/jpeg") + + @login_required() def img_data_json(request, image_id, conn=None, **kwargs): diff --git a/src/css/figure.css b/src/css/figure.css index 9ae08c934..86d216282 100644 --- a/src/css/figure.css +++ b/src/css/figure.css @@ -646,11 +646,6 @@ .z-projection { padding: 1px 5px 5px; } - .z-projection span{ - background-image: url("../images/projection20.png"); - width: 20px; - height: 20px; - } .crop-btn span{ background-image: url("../images/crop20.png"); width: 20px; diff --git a/src/index.html b/src/index.html index 026b07fd8..47a40678f 100644 --- a/src/index.html +++ b/src/index.html @@ -37,6 +37,7 @@ const IS_PUBLIC_USER = false; // Images larger than this are 'big' tiled images const MAX_PLANE_SIZE = 10188864; + const MAX_PROJECTION_BYTES = 1024 * 1024 * 256; const LOCAL_STORAGE_RECOVERED_FIGURE = "recoveredFigure" + USER_ID; diff --git a/src/js/models/panel_model.js b/src/js/models/panel_model.js index 4065f6657..a678d992e 100644 --- a/src/js/models/panel_model.js +++ b/src/js/models/panel_model.js @@ -560,18 +560,16 @@ } }, + // The max number of Planes we can do Z-projection, based on + // sizeXY, pixelType and the number of currently active Channels getMaxZprojPlanes: function() { - const MAX_BYTES = 1024 * 1024 * 256; let bytes_per_pixel = 4; if (this.get("pixel_range")) { bytes_per_pixel = Math.ceil(Math.log2(this.get("pixel_range")[1]) / 8.0); } - console.log('pixel_range', this.get("pixel_range"), 'bytes_per_pixel', bytes_per_pixel); let active_channels = this.get("channels").reduce((prev, channel) => channel.active ? prev + 1 : prev, 0); - console.log('active_channels', active_channels); - let max_planes = MAX_BYTES / bytes_per_pixel / (this.get('orig_width') * this.get('orig_height')); + let max_planes = MAX_PROJECTION_BYTES / bytes_per_pixel / (this.get('orig_width') * this.get('orig_height')); max_planes = Math.floor(max_planes / active_channels); - console.log("max_planes", max_planes); return max_planes; }, @@ -717,6 +715,17 @@ return this.get('orig_width') * this.get('orig_height') > MAX_PLANE_SIZE; }, + is_max_projection_exceeded: function() { + if (this.get('z_projection')) { + let maxProjPlanes = this.getMaxZprojPlanes(); + let zRange = parseInt(this.get('z_end')) - parseInt(this.get('z_start')); + if (zRange > maxProjPlanes) { + return true; + } + } + return false; + }, + get_img_src: function(force_no_padding) { var chs = this.get('channels'); var cStrings = chs.map(function(c, i){ @@ -740,7 +749,11 @@ // If BIG image, render scaled region var region = ""; - if (this.is_big_image()) { + if (this.is_max_projection_exceeded()) { + // if we're trying to do Z-projection with too many planes, + // this figure URL renders a suitable error message + baseUrl = BASE_WEBFIGURE_URL + 'max_projection_range_exceeded/' + } else if (this.is_big_image()) { baseUrl = BASE_WEBFIGURE_URL + 'render_scaled_region/'; var rect = this.getViewportAsRect(); // Render a region that is 1.5 x larger @@ -785,6 +798,11 @@ // offset of the img within it's frame get_vp_img_css: function(zoom, frame_w, frame_h, x, y) { + if (this.is_max_projection_exceeded()) { + // We want the warning placeholder image shown full width, mid-height + return {'left':0, 'top':(frame_h - frame_w)/2, 'width':frame_w, 'height': frame_w} + } + // For non-big images, we have the full plane in hand // css just shows the viewport region if (!this.is_big_image()) { diff --git a/src/js/views/right_panel_view.js b/src/js/views/right_panel_view.js index 4a428bade..99acfc896 100644 --- a/src/js/views/right_panel_view.js +++ b/src/js/views/right_panel_view.js @@ -1124,8 +1124,13 @@ // Don't currently support Z_projection on Big images. const z_projection_disabled = ((sum_sizeZ === this.models.length) || anyBig); + let sizeZ = this.models.getIfEqual('sizeZ'); + let show_max_Zrange = sizeZ && z_projection && max_z_proj_planes < sizeZ; + html = this.template({ + max_projection_bytes: MAX_PROJECTION_BYTES, zrange_max: max_z_proj_planes, + show_max_Zrange: show_max_Zrange, projectionIconUrl, 'z_projection_disabled': z_projection_disabled, 'rotation': rotation, diff --git a/src/js/views/scalebar_form_view.js b/src/js/views/scalebar_form_view.js index 970018916..094f77fd8 100644 --- a/src/js/views/scalebar_form_view.js +++ b/src/js/views/scalebar_form_view.js @@ -156,8 +156,9 @@ var ScalebarFormView = Backbone.View.extend({ } else { let pix_sze = m.get('pixel_size_x'); // account for floating point imprecision when comparing + pix_sze = pix_sze ? pix_sze.toFixed(10) : pix_sze; if (json.pixel_size_x != '-' && - json.pixel_size_x.toFixed(10) != pix_sze.toFixed(10)) { + json.pixel_size_x.toFixed(10) != pix_sze) { json.pixel_size_x = '-'; } if (json.pixel_size_symbol != m.get('pixel_size_x_symbol')) { diff --git a/src/templates/image_display_options.template.html b/src/templates/image_display_options.template.html index 52a9c5f7f..1175069b7 100644 --- a/src/templates/image_display_options.template.html +++ b/src/templates/image_display_options.template.html @@ -16,12 +16,15 @@ <% if(z_projection) { %>zp-btn-down<% } else if (typeof z_projection == 'boolean') { %><% } else { %>ch-btn-flat<% }%>" <% if (z_projection_disabled) { %>disabled="disabled"<% } %> > - - - -
-