Skip to content

Commit 2783a4c

Browse files
authored
Merge pull request #663 from will-moore/zarr
Zarr
2 parents acada9b + c1c5535 commit 2783a4c

22 files changed

+832
-141
lines changed

omero_figure/views.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def index(request, file_id=None, conn=None, **kwargs):
124124
'const BASE_OMEROWEB_URL = "%s";' % omeroweb_index)
125125
html = html.replace('const APP_ROOT_URL = "";',
126126
'const APP_ROOT_URL = "%s";' % figure_index)
127+
# Replace various other placeholder values with OMERO data/configs
127128
html = html.replace('const USER_ID = 0;', 'const USER_ID = %s' % user.id)
128129
html = html.replace('const PING_URL = "";',
129130
'const PING_URL = "%s";' % ping_url)
@@ -403,9 +404,13 @@ def save_web_figure(request, conn=None, **kwargs):
403404
try:
404405
json_data = json.loads(figure_json)
405406
for panel in json_data['panels']:
406-
image_ids.append(panel['imageId'])
407+
try:
408+
image_ids.append(int(panel['imageId']))
409+
except ValueError:
410+
# For NGFF images, the imageId is a string
411+
pass
407412
if len(image_ids) > 0:
408-
first_img_id = int(image_ids[0])
413+
first_img_id = image_ids[0]
409414
# remove duplicates
410415
image_ids = list(set(image_ids))
411416
# pretty-print json
@@ -442,7 +447,7 @@ def save_web_figure(request, conn=None, **kwargs):
442447
if file_id is None:
443448
# Create new file
444449
# Try to set Group context to the same as first image
445-
curr_gid = conn.SERVICE_OPTS.getOmeroGroup()
450+
curr_gid = conn.getEventContext().groupId
446451
i = None
447452
if first_img_id:
448453
i = conn.getObject("Image", first_img_id)

package-lock.json

Lines changed: 71 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
"jquery": "^3.6.0",
4040
"marked": "^4.2.12",
4141
"mousetrap": "^1.6.5",
42+
"ome-zarr.js": "^0.0.17",
4243
"raphael": "^2.3.0",
4344
"sortablejs": "^1.15.2",
4445
"underscore": "^1.13.8",
45-
"vite-plugin-html-inject": "^1.1.2"
46+
"vite-plugin-html-inject": "^1.1.2",
47+
"zarrita": "^0.5.4"
4648
}
4749
}

src/js/models/figure_model.js

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import { recoverFigureFromStorage,
88
clearFigureFromStorage,
99
figureConfirmDialog,
10-
getJson,
10+
getJsonWithCredentials,
1111
saveFigureToStorage,
1212
normalizeZProjectionBounds} from "../views/util";
13+
import { loadZarrForPanel } from "./zarr_utils";
1314

1415
// Version of the json file we're saving.
1516
// This only needs to increment when we make breaking changes (not linked to release versions.)
@@ -66,7 +67,7 @@
6667
var load_url = BASE_WEBFIGURE_URL + "load_web_figure/" + fileId + "/",
6768
self = this;
6869

69-
getJson(load_url).then(data => {
70+
getJsonWithCredentials(load_url).then(data => {
7071
data.fileId = fileId;
7172
self.load_from_JSON(data);
7273
self.set('unsaved', false);
@@ -310,7 +311,7 @@
310311
if (iids.length > 0) {
311312
var ptUrl = BASE_WEBFIGURE_URL + 'pixels_type/';
312313
ptUrl += '?image=' + iids.join('&image=');
313-
getJson(ptUrl).then(data => {
314+
getJsonWithCredentials(ptUrl).then(data => {
314315
// Update all panels
315316
// NB: By the time that this callback runs, the panels will have been created
316317
self.panels.forEach(function(p){
@@ -533,6 +534,11 @@
533534
// new image panels appropriately in a grid.
534535
var invalidIds = [];
535536
for (var i=0; i<iIds.length; i++) {
537+
console.log("Adding image", iIds[i]);
538+
if (iIds[i].includes("http")) {
539+
this.importZarrImage(iIds[i], coords, i);
540+
continue;
541+
}
536542
var imgId = iIds[i].replace("|", ""),
537543
validId = parseInt(imgId, 10) + "",
538544
imgDataUrl = BASE_WEBFIGURE_URL + 'imgData/' + validId + '/';
@@ -548,6 +554,73 @@
548554
}
549555
},
550556

557+
updateCoordsAndPanelCoords(panel_json, coords, index) {
558+
// update panel_json and coords
559+
coords.spacer = coords.spacer || panel_json.orig_width/20;
560+
var full_width = (coords.colCount * (panel_json.orig_width + coords.spacer)) - coords.spacer,
561+
full_height = (coords.rowCount * (panel_json.orig_height + coords.spacer)) - coords.spacer;
562+
coords.scale = coords.paper_width / (full_width + (2 * coords.spacer));
563+
coords.scale = Math.min(coords.scale, 1); // only scale down
564+
// For the FIRST IMAGE ONLY (coords.px etc undefined), we
565+
// need to work out where to start (px,py) now that we know size of panel
566+
// (assume all panels are same size)
567+
coords.px = coords.px || coords.c.x - (full_width * coords.scale)/2;
568+
coords.py = coords.py || coords.c.y - (full_height * coords.scale)/2;
569+
// calculate panel coordinates from index...
570+
var row = parseInt(index / coords.colCount, 10);
571+
var col = index % coords.colCount;
572+
var panelX = coords.px + ((panel_json.orig_width + coords.spacer) * coords.scale * col);
573+
var panelY = coords.py + ((panel_json.orig_height + coords.spacer) * coords.scale * row);
574+
575+
// update panel_json
576+
panel_json.x = panelX;
577+
panel_json.y = panelY;
578+
panel_json.width = panel_json.orig_width * coords.scale;
579+
panel_json.height = panel_json.orig_height * coords.scale;
580+
},
581+
582+
importZarrImage: async function(zarrUrl, coords, index) {
583+
if (zarrUrl.endsWith("/")) {
584+
zarrUrl = zarrUrl.slice(0, -1);
585+
}
586+
this.set('loading_count', this.get('loading_count') + 1);
587+
588+
let panel_json = await loadZarrForPanel(zarrUrl);
589+
if (panel_json.Error) {
590+
let zarrErr = panel_json.Error;
591+
for (let fmt of ["bioformats2raw.layout", "OME-Zarr Plates"]) {
592+
if (zarrErr.includes(fmt)) {
593+
zarrErr = `Error loading Zarr ${zarrUrl}:
594+
<p>${fmt} not currently supported.</p>
595+
<p>Please <a href="https://ome.github.io/ome-ngff-validator/?source=${encodeURIComponent(zarrUrl)}" target="_blank">
596+
open in the OME-NGFF Validator</a> to choose a single Image url.</p>
597+
`;
598+
}
599+
}
600+
if (zarrErr.includes("File not found")) {
601+
zarrErr = `Error loading Zarr from <br/>${zarrUrl}:
602+
<p>File not found (No <code>zarr.json</code> or <code>.zattrs</code>)</p>`;
603+
}
604+
figureConfirmDialog(
605+
"Zarr Load Error",
606+
zarrErr,
607+
["OK"]
608+
);
609+
// alert(`Error loading Zarr ${zarrUrl}: ${panel_json.Error}`);
610+
this.set('loading_count', this.get('loading_count') - 1);
611+
return;
612+
}
613+
614+
// coords (px, py etc) are incremented for each panel added
615+
this.updateCoordsAndPanelCoords(panel_json, coords, index)
616+
617+
this.set('loading_count', this.get('loading_count') - 1);
618+
// create Panel (and select it)
619+
// We do some additional processing in Panel.parse()
620+
this.panels.create(panel_json, {'parse': true}).set('selected', true);
621+
this.notifySelectionChange();
622+
},
623+
551624
importImage: function(imgDataUrl, coords, baseUrl, index) {
552625

553626
var self = this,
@@ -580,23 +653,6 @@
580653
return;
581654
}
582655

583-
coords.spacer = coords.spacer || data.size.width/20;
584-
var full_width = (coords.colCount * (data.size.width + coords.spacer)) - coords.spacer,
585-
full_height = (coords.rowCount * (data.size.height + coords.spacer)) - coords.spacer;
586-
coords.scale = coords.paper_width / (full_width + (2 * coords.spacer));
587-
coords.scale = Math.min(coords.scale, 1); // only scale down
588-
// For the FIRST IMAGE ONLY (coords.px etc undefined), we
589-
// need to work out where to start (px,py) now that we know size of panel
590-
// (assume all panels are same size)
591-
coords.px = coords.px || coords.c.x - (full_width * coords.scale)/2;
592-
coords.py = coords.py || coords.c.y - (full_height * coords.scale)/2;
593-
594-
// calculate panel coordinates from index...
595-
var row = parseInt(index / coords.colCount, 10);
596-
var col = index % coords.colCount;
597-
var panelX = coords.px + ((data.size.width + coords.spacer) * coords.scale * col);
598-
var panelY = coords.py + ((data.size.height + coords.spacer) * coords.scale * row);
599-
600656
// ****** This is the Data Model ******
601657
//-------------------------------------
602658
// Any changes here will create a new version
@@ -617,8 +673,8 @@
617673
'channels': data.channels,
618674
'orig_width': data.size.width,
619675
'orig_height': data.size.height,
620-
'x': panelX,
621-
'y': panelY,
676+
'x': 0,
677+
'y': 0,
622678
'datasetName': data.meta.datasetName,
623679
'datasetId': data.meta.datasetId,
624680
'pixel_size_x': data.pixel_size.valueX,
@@ -636,6 +692,10 @@
636692
if (baseUrl) {
637693
n.baseUrl = baseUrl;
638694
}
695+
696+
// coords (px, py etc) are incremented for each panel added
697+
self.updateCoordsAndPanelCoords(n, coords, index);
698+
639699
// create Panel (and select it)
640700
// We do some additional processing in Panel.parse()
641701
self.panels.create(n, {'parse': true}).set('selected', true);

src/js/models/panel_model.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import _ from "underscore";
44
import $ from "jquery";
55
import { rotatePoint, figureConfirmDialog, normalizeZProjectionBounds } from "../views/util";
6+
import {renderZarrToSrc} from "./zarr_utils";
67

78
// Corresponds to css - allows us to calculate size of labels
89
var LINE_HEIGHT = 1.43;
@@ -1032,7 +1033,33 @@
10321033
return this.get('orig_width') * this.get('orig_height') > MAX_PLANE_SIZE;
10331034
},
10341035

1035-
get_img_src: function(force_no_padding) {
1036+
get_zarr_img_src: async function(force_no_padding, targetSize) {
1037+
var rect;
1038+
if (this.is_big_image()) {
1039+
rect = this.getViewportAsRect();
1040+
if (!force_no_padding) {
1041+
var length = Math.max(rect.width, rect.height) * 1.5;
1042+
rect.x = rect.x - ((length - rect.width) / 2);
1043+
rect.y = rect.y - ((length - rect.height) / 2);
1044+
rect.width = length;
1045+
rect.height = length;
1046+
}
1047+
} else {
1048+
// Since we render the whole plane every time (no 'rect' here), caching will prevent reloading on pan/zoom.
1049+
// as long as targetSize doesn't change. So we can afford to render at higher resolution...
1050+
targetSize = Math.max(targetSize, 2000);
1051+
}
1052+
return renderZarrToSrc(this.get('imageId'), this.get('zarr'), this.get('theZ'), this.get('theT'), this.get('channels'), rect, targetSize);
1053+
},
1054+
1055+
get_img_src: async function(force_no_padding) {
1056+
// async function since zarr src is the rendered image data
1057+
if (this.get("zarr")) {
1058+
// use current size to choose resolution level...
1059+
let targetSize = 4 * Math.max(this.get('width'), this.get('height'));
1060+
targetSize = Math.max(targetSize, 500);
1061+
return this.get_zarr_img_src(force_no_padding, targetSize);
1062+
}
10361063
var chs = this.get('channels');
10371064
var cStrings = chs.map(function(c, i){
10381065
return (c.active ? '' : '-') + (1+i) + "|" + c.window.start + ":" + c.window.end + "$" + c.color;
@@ -1500,7 +1527,11 @@
15001527

15011528
createLabelsFromTags: function(options) {
15021529
// Loads Tags for selected images and creates labels
1503-
var image_ids = this.map(function(s){return s.get('imageId')})
1530+
var image_ids = this.map(function(s){return s.get('imageId')});
1531+
image_ids = _.uniq(image_ids).filter(id => !isNaN(id)); // ignore zarr images
1532+
if (image_ids.length === 0) {
1533+
return;
1534+
}
15041535
image_ids = "image=" + image_ids.join("&image=");
15051536
// TODO: Use /api/ when annotations is supported
15061537
var url = WEBINDEX_URL + "api/annotations/?type=tag&parents=true&limit=1000&" + image_ids;

0 commit comments

Comments
 (0)