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

Don't use group -1 for luts_png #603

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ Usage
For running omero-web in production with NGINX, see See: `OMERO.web install`_ documentation.
To run in development mode, see below.

LUTs caching
------------

The look-up tables png available at `/webgateway/luts_png/` is generated from `rgb` values
cached in https://github.com/ome/omero-web/blob/master/omeroweb/webgateway/static/webgateway/json/luts.json.
The LUTs in the `/luts_png/` will always correspond to the LUTs on the server as available in JSON
from `/webgateway/luts/`.
If new LUTs are added to the server and are not found in the `luts.json` then the `/luts_png/` will
Copy link
Member

Choose a reason for hiding this comment

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

again confusing.
luts.json is a visual representation i.e. name and associated png

Copy link
Member Author

Choose a reason for hiding this comment

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

No, luts.json is a json file (see this PR).

Copy link
Member

Choose a reason for hiding this comment

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

I know, I should have said "holds a visual representation but If new LUTs are added to the server and are not found in the `luts.json is not clear

Copy link
Member Author

Choose a reason for hiding this comment

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

I rewrote the README - hopefully more clear now?

show a blank placeholder for that LUT.
If you wish to update the cached `luts.json`, you can copy the response from `/webgateway/luts/?rgb=true`
and save it to `/webgateway/static/webgateway/json/luts.json`.

Contributing
------------

Expand Down
1 change: 1 addition & 0 deletions omeroweb/webgateway/static/webgateway/json/luts.json

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions omeroweb/webgateway/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import zipfile
import shutil
import logging
import numpy as np

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -233,3 +234,41 @@ def points_string_to_XY_list(string):
x, y = xy.split(",")
xyList.append((float(x.strip()), float(y.strip())))
return xyList


def load_lut_to_rgb(conn, orig_file_id):
"""
Loads the LUT original file and converts to numpy array

Returns numpy array with shape (256 x 3) for rgb values.
"""
orig_file = conn.getObject("OriginalFile", orig_file_id)
lut_data = bytearray()
# Collect the LUT data in byte form
for chunk in orig_file.getFileInChunks():
lut_data.extend(chunk)

rgb_values = np.zeros((256, 3), dtype="uint8")
if len(lut_data) in [768, 800]:
lut_arr = np.array(lut_data, dtype="uint8")[-768:]
rgb_values = lut_arr.reshape(3, 256).T
else:
lut_data = lut_data.decode()
r, g, b = [], [], []

lines = lut_data.split("\n")
sep = None
if "\t" in lines[0]:
sep = "\t"
for line in lines:
val = line.split(sep)
if len(val) < 3 or not val[-1].isnumeric():
continue
r.append(int(val[-3]))
g.append(int(val[-2]))
b.append(int(val[-1]))
rgb_values[:, 0] = np.array(r)
rgb_values[:, 1] = np.array(g)
rgb_values[:, 2] = np.array(b)

return rgb_values
105 changes: 49 additions & 56 deletions omeroweb/webgateway/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
StreamingHttpResponse,
HttpResponseNotFound,
)

from django.core.cache import cache
from django.contrib.staticfiles.storage import staticfiles_storage
from django.views.decorators.http import require_POST
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.decorators import method_decorator
Expand All @@ -45,7 +44,7 @@
from wsgiref.util import FileWrapper
from omero.rtypes import rlong, unwrap
from omero.constants.namespaces import NSBULKANNOTATIONS
from .util import points_string_to_XY_list, xy_list_to_bbox
from .util import points_string_to_XY_list, xy_list_to_bbox, load_lut_to_rgb
from .plategrid import PlateGrid
from omeroweb.version import omeroweb_buildyear as build_year
from .marshal import imageMarshal, shapeMarshal, rgb_int2rgba
Expand Down Expand Up @@ -2069,11 +2068,11 @@ def save_image_rdef_json(request, iid, conn=None, **kwargs):
return HttpJavascriptResponse(json_data)


@login_required()
@login_required(omero_group=None)
@jsonp
def listLuts_json(request, conn=None, **kwargs):
"""
Lists lookup tables 'LUTs' availble for rendering.
Lists lookup tables 'LUTs' available for rendering.

We include 'png_index' which is the index of each LUT within the
static/webgateway/img/luts_10.png or -1 if LUT is not found.
Expand All @@ -2088,26 +2087,47 @@ def listLuts_json(request, conn=None, **kwargs):
luts = scriptService.getScriptsByMimetype("text/x-lut")
luts.sort(key=lambda x: x.name.val.lower())
rv, all_luts = [], []

luts_by_pathname = {}
include_rgb = request.GET.get("rgb") == "true"
if include_rgb:
json_path = staticfiles_storage.path("webgateway/json/luts.json")
with open(json_path) as json_file:
luts_json = json.load(json_file)
for lut in luts_json["luts"]:
pathname = lut["path"] + lut["name"]
luts_by_pathname[pathname] = lut

for i, lut in enumerate(luts):
lutsrc = lut.path.val + lut.name.val
all_luts.append(lutsrc)
idx = LUTS_IN_PNG.index(lutsrc) if lutsrc in LUTS_IN_PNG else -1
rv.append(
{
"id": lut.id.val,
"path": lut.path.val,
"name": lut.name.val,
"size": unwrap(lut.size),
"png_index": idx,
"png_index_new": i,
}
)
# rgb = load_lut_to_rgb(conn, lut.id.val)
lut_data = {
"id": lut.id.val,
Copy link
Member

Choose a reason for hiding this comment

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

The LUT ID might vary from server to server. This is not a problem per se especially as you are using path+name for normalizing but this means there is a mismatch between the cached JSON file and this endpoint - which can be seen for instance by comparing https://merge-ci.openmicroscopy.org/web/webgateway/luts/ with https://merge-ci.openmicroscopy.org/web/static/webgateway/json/luts.json

Should id not omitted in the cache?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes ids will change, but are harmless. If we want to allow the easy copying of /webgateway/luts/?rgb=true into the static luts.json but ALSO exclude ids, then maybe we need another parameter ?ids=false. Easy to do if worth it?

"path": lut.path.val,
"name": lut.name.val,
"size": unwrap(lut.size),
"png_index": idx,
"png_index_new": i,
}
if include_rgb:
# If we have the LUT cached, use that...
pathname = lut.path.val + lut.name.val
if pathname in luts_by_pathname:
lut_data["rgb"] = luts_by_pathname[pathname].get("rgb")
else:
# ...otherwise load the original file
rgb = load_lut_to_rgb(conn, lut.id.val)
lut_data["rgb"] = rgb.tolist()
rv.append(lut_data)

all_luts.append("gradient.png")

return {"luts": rv, "png_luts": LUTS_IN_PNG, "png_luts_new": all_luts}


@login_required()
@login_required(omero_group=None)
def luts_png(request, conn=None, **kwargs):
"""
Generates the LUT png used for preview and selection of LUT. The png is
Expand All @@ -2126,47 +2146,24 @@ def luts_png(request, conn=None, **kwargs):
"""
scriptService = conn.getScriptService()
luts = scriptService.getScriptsByMimetype("text/x-lut")
luts.sort(key=lambda x: x.name.val)
luts_path = []
for lut in luts:
luts_path.append(lut.path.val + lut.name.val)
luts_hash = hash("\n".join(luts_path))
cache_key = f"lut_hash_{luts_hash}"
luts.sort(key=lambda x: x.name.val.lower())

cached_image = cache.get(cache_key)
if cached_image:
return HttpResponse(cached_image, content_type="image/png")
luts_by_pathname = {}
# Load cached LUTS json to get rgb values...
json_path = staticfiles_storage.path("webgateway/json/luts.json")
with open(json_path) as json_file:
luts_json = json.load(json_file)
for lut in luts_json["luts"]:
pathname = lut["path"] + lut["name"]
luts_by_pathname[pathname] = lut

# Generate the LUT, fourth png channel set to 255
new_img = numpy.zeros((10 * (len(luts) + 1), 256, 4), dtype="uint8") + 255
for i, lut in enumerate(luts):
orig_file = conn.getObject("OriginalFile", lut.getId()._val)
lut_data = bytearray()
# Collect the LUT data in byte form
for chunk in orig_file.getFileInChunks():
lut_data.extend(chunk)

if len(lut_data) in [768, 800]:
lut_arr = numpy.array(lut_data, dtype="uint8")[-768:]
new_img[(i * 10) : (i + 1) * 10, :, :3] = lut_arr.reshape(3, 256).T
else:
lut_data = lut_data.decode()
r, g, b = [], [], []

lines = lut_data.split("\n")
sep = None
if "\t" in lines[0]:
sep = "\t"
for line in lines:
val = line.split(sep)
if len(val) < 3 or not val[-1].isnumeric():
continue
r.append(int(val[-3]))
g.append(int(val[-2]))
b.append(int(val[-1]))
new_img[(i * 10) : (i + 1) * 10, :, 0] = numpy.array(r)
new_img[(i * 10) : (i + 1) * 10, :, 1] = numpy.array(g)
new_img[(i * 10) : (i + 1) * 10, :, 2] = numpy.array(b)
pathname = lut.path.val + lut.name.val
if pathname in luts_by_pathname:
lut_rgb = luts_by_pathname[pathname].get("rgb")
new_img[(i * 10) : ((i + 1) * 10), :, :3] = lut_rgb

# Set the last row for the channel sliders transparent gradient
new_img[-10:] = 0
Expand All @@ -2178,10 +2175,6 @@ def luts_png(request, conn=None, **kwargs):
image.save(buffer, format="PNG")
buffer.seek(0)

# Cache the image using the version-based key
# Cache timeout set to None (no timeout)
cache.set(cache_key, buffer.getvalue(), None)

return HttpResponse(buffer.getvalue(), content_type="image/png")


Expand Down
Loading