Skip to content
Closed
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
2 changes: 2 additions & 0 deletions plugin/omero_iviewer/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@
# Find the index of an ROI within all ROIs for the Image (for pagination)
re_path(r'^(?P<obj_type>(roi|shape))/(?P<obj_id>[0-9]+)/page_data/$',
views.roi_page_data, name='omero_iviewer_roi_page_data'),
re_path(r"^render_labels/(?P<iid>[0-9]+)/(?:(?P<z>[0-9]+)/)?(?:(?P<t>[0-9]+)/)?$",
views.render_labels, name="omero_iviewer_render_labels",),
]
49 changes: 48 additions & 1 deletion plugin/omero_iviewer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

from io import BytesIO
from PIL import Image
from django.shortcuts import render
from django.http import JsonResponse, Http404
from django.http import JsonResponse, Http404, HttpResponse
from django.conf import settings
from django.urls import reverse

Expand All @@ -38,6 +40,8 @@
from omero_sys_ParametersI import ParametersI
import omero.util.pixelstypetopython as pixelstypetopython
from omeroweb.webclient.show import get_image_roi_id_for_shape
from datetime import datetime
import numpy as np

from .version import __version__
from omero_version import omero_version
Expand Down Expand Up @@ -913,3 +917,46 @@ def shape_stats(request, conn=None, **kwargs):
return JsonResponse({"error": api_exception.message})
except Exception as stats_call_exception:
return JsonResponse({"error": repr(stats_call_exception)})

@login_required()
def render_labels(request, iid, z=None, t=None, conn=None, **kwargs):

img = conn.getObject("Image", iid)
# get the first channel as np array
arr = img.getPrimaryPixels().getPlane(theZ=int(z), theC=0, theT=int(t))
# find smallest non-zero value in the array
a = np.array(arr)
min_nonzero = np.min(a[np.nonzero(a)])
max_value = np.max(a)
print("min_nonzero:", min_nonzero, "max_value:", max_value)

rgba_array = np.zeros((img.getSizeY(), img.getSizeX(), 4), dtype=np.uint8)
# create a simple LUT mapping each label to a color
try:
import distinctipy2
label_colors = distinctipy2.get_colors(max_value - min_nonzero + 1)
except ImportError:
# fallback to random colors
import random
random.seed(0)
label_colors = []
for _ in range(min_nonzero, max_value + 1):
label_colors.append([random.random(), random.random(),
random.random()])
lut = {}

starttime = datetime.now()
for val in range(min_nonzero, max_value + 1):
rgb = [int(c * 255) for c in label_colors[val - min_nonzero]]
lut[val] = (rgb[0], rgb[1], rgb[2])
mask = a == val
rgba_array[mask] = [rgb[0], rgb[1], rgb[2], 255]
# convert rgba_array to PNG
print("Label rendering took:", datetime.now() - starttime)
rgba_image = Image.fromarray(rgba_array, mode="RGBA")

output = BytesIO()
rgba_image.save(output, "png")
png_data = output.getvalue()
output.close()
return HttpResponse(png_data, content_type="image/png")
3 changes: 2 additions & 1 deletion src/viewers/ol3-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ export default class Ol3Viewer extends EventSubscriber {
this.image_config.regions_info, 'ready').subscribe(
(newValue, oldValue) => {
if (this.viewer === null) return;
this.viewer.removeRegions();
// Don't remove existing layers
// this.viewer.removeRegions();
if (newValue) {
this.initRegions();
delete this.image_config.regions_info.tmp_data;
Expand Down
58 changes: 55 additions & 3 deletions src/viewers/viewer/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,35 @@ class Viewer extends OlObject {
});
source.changeChannelRange(initialChannels, false);

// check for ?labelimage=123 query parameter...
var seg_source;
const params1 = new URLSearchParams(window.location.search);
const labelImageId = params1.get('labelimage');
if (labelImageId && !isNaN(parseInt(labelImageId))) {
// NB: assume the same dims etc as the main image for now...
seg_source = new OmeroImage({
// opaque: true,
label_image: true,
server : this.getServer(),
uri : this.getPrefixedURI(WEBGATEWAY),
image: parseInt(labelImageId),
width: dims['width'],
height: dims['height'],
size_t: dims.t,
plane: initialPlane,
time: initialTime,
channels: channels,
resolutions: zoom > 1 ? zoomLevelScaling : [1],
img_proj: parsedInitialProjection,
img_model: initialModel,
tiled: isTiled,
tile_size: isTiled && this.supportsOmeroServerVersion("5.4.4") ?
DEFAULT_TILE_DIMS :
this.image_info_['tile_size'] ?
this.image_info_['tile_size'] : null
});
};

var defaultZoom = zoom > 1 ? zoomLevelScaling[0] : 1;
var actualZoom;
var initialZoom = this.getInitialRequestParam(REQUEST_PARAMS.ZOOM);
Expand Down Expand Up @@ -592,14 +621,19 @@ class Viewer extends OlObject {
}


const layers = [new Tile({source: source})];
if (seg_source) {
layers.push(new Tile({source: seg_source, opaque: false}));
}
// finally construct the open layers map object
this.viewer_ = new OlMap({
logo: false,
controls: controls,
interactions: interactions,
layers: [new Tile({source: source})],
layers: layers,
target: this.container_,
view: view
view: view,
renderer: 'canvas'
});

// enable bird's eye view
Expand Down Expand Up @@ -1240,7 +1274,25 @@ class Viewer extends OlObject {
this.affectImageRender(omeroImage.use_tiled_retrieval_ && key === 'c');

// update regions (if necessary)
if (this.getRegionsLayer()) this.getRegions().changed();
console.log("Z/T changed, updating regions layer", this.getRegionsLayer());
// this just gets the last layer... which could be our labels layer
const regionsLayer = this.getRegionsLayer();
if (regionsLayer) {
console.log("Regions layer present, updating", this.getRegions());
let seg_source = regionsLayer.getSource();
console.log("Regions source", seg_source, seg_source[setter]);

var renderer = this.viewer_.getRenderer(regionsLayer);
var imageCanvas = renderer.getLayerRenderer(regionsLayer).getImage();
imageCanvas.getContext('2d').clearRect(0, 0, imageCanvas.width, imageCanvas.height);
regionsLayer.getSource().tileCache.clear();

seg_source[setter](key !== 'c' ? values[0] : values);

seg_source.cache_version_++;
this.getRegions().changed();
}


// update popup (hide it if shape no longer visible)
this.viewer_.getOverlays().forEach(o => {
Expand Down
23 changes: 21 additions & 2 deletions src/viewers/viewer/source/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ const OmeroImage = function(options) {
if (typeof this.id_ !== 'number' || this.id_ <= 0)
console.error("Image id must be a strictly positive integer");

/**
* If label_image, we add ?label=true to the tile url requests
*/
this.label_image_ = opts.label_image || false;

/**
* an internal cache version which helps us invalidate
* without clearing so that newer images are rendered 'on top' of olders
Expand Down Expand Up @@ -256,8 +261,15 @@ const OmeroImage = function(options) {
function tileUrlFunction(tileCoord, pixelRatio, projection) {
if (!tileCoord) return undefined;

let webgatewayPath = this.uri_['full'];
if (this.label_image_) {
// e.g. webgateway/render_image_region/ -> iviewer/render_labels_region/
// or webgateway/render_image/ -> iviewer/render_labels/
webgatewayPath = webgatewayPath.replace("webgateway/render_image", "iviewer/render_labels");
}

var url =
this.server_['full'] + "/" + this.uri_['full'] + '/' +
this.server_['full'] + "/" + webgatewayPath + '/' +
this.id_ + '/' + this.plane_ + '/' + this.time_ + '/?';

if (this.tiled_ || this.use_tiled_retrieval_) {
Expand All @@ -277,6 +289,11 @@ const OmeroImage = function(options) {
url += ',' + this.tileGrid.tileSize_[0] + ',' +
this.tileGrid.tileSize_[1] + '&';
}
if (this.label_image_) {
url += 'label=true';
// no further params needed for label images
return url;
}

// maps parameter (incl. inverted)
var maps = [];
Expand Down Expand Up @@ -351,7 +368,9 @@ const OmeroImage = function(options) {
crossOrigin: opts.crossOrigin,
tileClass: ImageTile,
tileGrid: tileGrid,
tileUrlFunction: this.tileUrlFunction_
tileUrlFunction: this.tileUrlFunction_,
opacity: opts.opacity !== undefined ? opts.opacity : 1.0,
opaque: opts.opaque !== undefined ? opts.opaque : true,
});
};
inherits(OmeroImage, TileImage);
Expand Down
Loading