Skip to content
Merged

Zarr #663

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
44e0e5d
First step - mostly add a zarr image
will-moore Dec 2, 2024
7ca2f61
Load all .zarray metadata. Convert pixelType
will-moore Dec 3, 2024
746bcd3
npm install zarrita@next
will-moore Dec 3, 2024
fc9fe8a
Zarr rendering in panel_view.js
will-moore Dec 4, 2024
6b45fa2
Simple caching of zarr stores and data
will-moore Dec 4, 2024
3bbc15c
Handle right panel setting of src async
will-moore Dec 4, 2024
4f53f7d
Handle async get_img_src() everywhere
will-moore Dec 5, 2024
f9e76a7
Add APP_SERVED_BY_OMERO global js variable
will-moore Feb 11, 2025
f606f96
Handle missing 'omero' metadata from zarr
will-moore Feb 14, 2025
f349124
Update to use ome-zarr.js 0.0.6
will-moore Feb 18, 2025
a0da4ea
Fix layout of multiple Zarr images when added together
will-moore Feb 20, 2025
f6a9df1
Fix render_scaled_region behaviour for BIG zarr images
will-moore Feb 20, 2025
31352eb
Fix saving of NGFF figures to OMERO
will-moore Mar 6, 2025
ae034d4
isDark(color) handles invalid color
will-moore Mar 13, 2025
e3c969a
Fix bug in adding OMERO images
will-moore Mar 17, 2025
5de8480
Remove baseUrl support in Open with. Zarrs links to ngff-validatator
will-moore Mar 17, 2025
7a072de
Handle 'scale' to populate pixel sizes
will-moore Mar 17, 2025
a36d7d2
Remove some console.log
will-moore Mar 17, 2025
b9c1e21
Fix LUT background on channel buttons and slider in OMERO
will-moore Mar 21, 2025
f834d2b
Handle Zarr v3. ONLY save array.shape and array.dtype to figure JSON
will-moore Mar 28, 2025
39ab0b6
Handle opening of OME-Zarr v0.3 images
will-moore Mar 28, 2025
ff86010
Fix Luts display, update zarrita and ome-zarr.js
will-moore Jan 22, 2026
89c41f0
Remove APP_SERVED_BY_OMERO and Luts display logic
will-moore Jan 22, 2026
f6888ad
Remove unsupported importFromRemote() for adding public remote OMERO …
will-moore Jan 22, 2026
d58c7a4
Handle zarr urls that end in /
will-moore Jan 22, 2026
ea2eb45
Info panel for zarr imagess shows URL instead of Image ID and Edit ID…
will-moore Jan 22, 2026
9f9c52d
Improve error handling when loading Zarr images
will-moore Jan 22, 2026
5974b66
Store zarr_version in figure.json
will-moore Jan 22, 2026
fd5121a
Avoid duplicate loading of chunks when rendering
will-moore Jan 22, 2026
4d16cab
Write OME-Zarr version to figure.json
will-moore Jan 23, 2026
c5ee8f1
renderZarrToSrc() picks closest to targetSize (2 x size on page)
will-moore Jan 29, 2026
f16ad18
Handle images with Z-downsampling, and with missing 'omero' metadata
will-moore Jan 29, 2026
2373cd1
Handle OME-Zarr 't' axes with units to populate deltaT timestamps
will-moore Jan 29, 2026
b0c3d14
Show spinner on figure panels while images are loading
will-moore Jan 30, 2026
300d0ff
Labels from KVP or Tags ignores zarr images
will-moore Jan 30, 2026
81d4a41
Rename utils.getJson() to getJsonWithCredentials()
will-moore Jan 30, 2026
7522bb4
Use 'http' to identify zarr URLs rather than 'zarr'
will-moore Jan 30, 2026
32b6c1b
Handle bf2raw, plate, CORS and File not found for Zarr
will-moore Jan 30, 2026
f012966
boost targetSize for zarr rendering to 4 x size of panel
will-moore Feb 4, 2026
1a0bf21
Use higher targetSize for non-big images
will-moore Feb 4, 2026
03062bc
Handle any missing values in omero.channels
will-moore Feb 6, 2026
815a82d
fixes for zarr v0.2 images
will-moore Feb 26, 2026
2f986f5
Merge remote-tracking branch 'origin/master' into zarr
will-moore Mar 2, 2026
c1c5535
Merge remote-tracking branch 'origin/master' into zarr
will-moore Mar 12, 2026
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
11 changes: 8 additions & 3 deletions omero_figure/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def index(request, file_id=None, conn=None, **kwargs):
'const BASE_OMEROWEB_URL = "%s";' % omeroweb_index)
html = html.replace('const APP_ROOT_URL = "";',
'const APP_ROOT_URL = "%s";' % figure_index)
# Replace various other placeholder values with OMERO data/configs
html = html.replace('const USER_ID = 0;', 'const USER_ID = %s' % user.id)
html = html.replace('const PING_URL = "";',
'const PING_URL = "%s";' % ping_url)
Expand Down Expand Up @@ -403,9 +404,13 @@ def save_web_figure(request, conn=None, **kwargs):
try:
json_data = json.loads(figure_json)
for panel in json_data['panels']:
image_ids.append(panel['imageId'])
try:
image_ids.append(int(panel['imageId']))
except ValueError:
# For NGFF images, the imageId is a string
pass
if len(image_ids) > 0:
first_img_id = int(image_ids[0])
first_img_id = image_ids[0]
# remove duplicates
image_ids = list(set(image_ids))
# pretty-print json
Expand Down Expand Up @@ -442,7 +447,7 @@ def save_web_figure(request, conn=None, **kwargs):
if file_id is None:
# Create new file
# Try to set Group context to the same as first image
curr_gid = conn.SERVICE_OPTS.getOmeroGroup()
curr_gid = conn.getEventContext().groupId
i = None
if first_img_id:
i = conn.getObject("Image", first_img_id)
Expand Down
72 changes: 71 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
"jquery": "^3.6.0",
"marked": "^4.2.12",
"mousetrap": "^1.6.5",
"ome-zarr.js": "^0.0.17",
"raphael": "^2.3.0",
"sortablejs": "^1.15.2",
"underscore": "^1.13.8",
"vite-plugin-html-inject": "^1.1.2"
"vite-plugin-html-inject": "^1.1.2",
"zarrita": "^0.5.4"
}
}
104 changes: 82 additions & 22 deletions src/js/models/figure_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
import { recoverFigureFromStorage,
clearFigureFromStorage,
figureConfirmDialog,
getJson,
getJsonWithCredentials,
saveFigureToStorage,
normalizeZProjectionBounds} from "../views/util";
import { loadZarrForPanel } from "./zarr_utils";

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

getJson(load_url).then(data => {
getJsonWithCredentials(load_url).then(data => {
data.fileId = fileId;
self.load_from_JSON(data);
self.set('unsaved', false);
Expand Down Expand Up @@ -310,7 +311,7 @@
if (iids.length > 0) {
var ptUrl = BASE_WEBFIGURE_URL + 'pixels_type/';
ptUrl += '?image=' + iids.join('&image=');
getJson(ptUrl).then(data => {
getJsonWithCredentials(ptUrl).then(data => {
// Update all panels
// NB: By the time that this callback runs, the panels will have been created
self.panels.forEach(function(p){
Expand Down Expand Up @@ -533,6 +534,11 @@
// new image panels appropriately in a grid.
var invalidIds = [];
for (var i=0; i<iIds.length; i++) {
console.log("Adding image", iIds[i]);
if (iIds[i].includes("http")) {
this.importZarrImage(iIds[i], coords, i);
continue;
}
var imgId = iIds[i].replace("|", ""),
validId = parseInt(imgId, 10) + "",
imgDataUrl = BASE_WEBFIGURE_URL + 'imgData/' + validId + '/';
Expand All @@ -548,6 +554,73 @@
}
},

updateCoordsAndPanelCoords(panel_json, coords, index) {
// update panel_json and coords
coords.spacer = coords.spacer || panel_json.orig_width/20;
var full_width = (coords.colCount * (panel_json.orig_width + coords.spacer)) - coords.spacer,
full_height = (coords.rowCount * (panel_json.orig_height + coords.spacer)) - coords.spacer;
coords.scale = coords.paper_width / (full_width + (2 * coords.spacer));
coords.scale = Math.min(coords.scale, 1); // only scale down
// For the FIRST IMAGE ONLY (coords.px etc undefined), we
// need to work out where to start (px,py) now that we know size of panel
// (assume all panels are same size)
coords.px = coords.px || coords.c.x - (full_width * coords.scale)/2;
coords.py = coords.py || coords.c.y - (full_height * coords.scale)/2;
// calculate panel coordinates from index...
var row = parseInt(index / coords.colCount, 10);
var col = index % coords.colCount;
var panelX = coords.px + ((panel_json.orig_width + coords.spacer) * coords.scale * col);
var panelY = coords.py + ((panel_json.orig_height + coords.spacer) * coords.scale * row);

// update panel_json
panel_json.x = panelX;
panel_json.y = panelY;
panel_json.width = panel_json.orig_width * coords.scale;
panel_json.height = panel_json.orig_height * coords.scale;
},

importZarrImage: async function(zarrUrl, coords, index) {
if (zarrUrl.endsWith("/")) {
zarrUrl = zarrUrl.slice(0, -1);
}
this.set('loading_count', this.get('loading_count') + 1);

let panel_json = await loadZarrForPanel(zarrUrl);
if (panel_json.Error) {
let zarrErr = panel_json.Error;
for (let fmt of ["bioformats2raw.layout", "OME-Zarr Plates"]) {
if (zarrErr.includes(fmt)) {
zarrErr = `Error loading Zarr ${zarrUrl}:
<p>${fmt} not currently supported.</p>
<p>Please <a href="https://ome.github.io/ome-ngff-validator/?source=${encodeURIComponent(zarrUrl)}" target="_blank">
open in the OME-NGFF Validator</a> to choose a single Image url.</p>
`;
}
}
if (zarrErr.includes("File not found")) {
zarrErr = `Error loading Zarr from <br/>${zarrUrl}:
<p>File not found (No <code>zarr.json</code> or <code>.zattrs</code>)</p>`;
}
figureConfirmDialog(
"Zarr Load Error",
zarrErr,
["OK"]
);
// alert(`Error loading Zarr ${zarrUrl}: ${panel_json.Error}`);
this.set('loading_count', this.get('loading_count') - 1);
return;
}

// coords (px, py etc) are incremented for each panel added
this.updateCoordsAndPanelCoords(panel_json, coords, index)

this.set('loading_count', this.get('loading_count') - 1);
// create Panel (and select it)
// We do some additional processing in Panel.parse()
this.panels.create(panel_json, {'parse': true}).set('selected', true);
this.notifySelectionChange();
},

importImage: function(imgDataUrl, coords, baseUrl, index) {

var self = this,
Expand Down Expand Up @@ -580,23 +653,6 @@
return;
}

coords.spacer = coords.spacer || data.size.width/20;
var full_width = (coords.colCount * (data.size.width + coords.spacer)) - coords.spacer,
full_height = (coords.rowCount * (data.size.height + coords.spacer)) - coords.spacer;
coords.scale = coords.paper_width / (full_width + (2 * coords.spacer));
coords.scale = Math.min(coords.scale, 1); // only scale down
// For the FIRST IMAGE ONLY (coords.px etc undefined), we
// need to work out where to start (px,py) now that we know size of panel
// (assume all panels are same size)
coords.px = coords.px || coords.c.x - (full_width * coords.scale)/2;
coords.py = coords.py || coords.c.y - (full_height * coords.scale)/2;

// calculate panel coordinates from index...
var row = parseInt(index / coords.colCount, 10);
var col = index % coords.colCount;
var panelX = coords.px + ((data.size.width + coords.spacer) * coords.scale * col);
var panelY = coords.py + ((data.size.height + coords.spacer) * coords.scale * row);

// ****** This is the Data Model ******
//-------------------------------------
// Any changes here will create a new version
Expand All @@ -617,8 +673,8 @@
'channels': data.channels,
'orig_width': data.size.width,
'orig_height': data.size.height,
'x': panelX,
'y': panelY,
'x': 0,
'y': 0,
'datasetName': data.meta.datasetName,
'datasetId': data.meta.datasetId,
'pixel_size_x': data.pixel_size.valueX,
Expand All @@ -636,6 +692,10 @@
if (baseUrl) {
n.baseUrl = baseUrl;
}

// coords (px, py etc) are incremented for each panel added
self.updateCoordsAndPanelCoords(n, coords, index);

// create Panel (and select it)
// We do some additional processing in Panel.parse()
self.panels.create(n, {'parse': true}).set('selected', true);
Expand Down
35 changes: 33 additions & 2 deletions src/js/models/panel_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import _ from "underscore";
import $ from "jquery";
import { rotatePoint, figureConfirmDialog, normalizeZProjectionBounds } from "../views/util";
import {renderZarrToSrc} from "./zarr_utils";

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

get_img_src: function(force_no_padding) {
get_zarr_img_src: async function(force_no_padding, targetSize) {
var rect;
if (this.is_big_image()) {
rect = this.getViewportAsRect();
if (!force_no_padding) {
var length = Math.max(rect.width, rect.height) * 1.5;
rect.x = rect.x - ((length - rect.width) / 2);
rect.y = rect.y - ((length - rect.height) / 2);
rect.width = length;
rect.height = length;
}
} else {
// Since we render the whole plane every time (no 'rect' here), caching will prevent reloading on pan/zoom.
// as long as targetSize doesn't change. So we can afford to render at higher resolution...
targetSize = Math.max(targetSize, 2000);
}
return renderZarrToSrc(this.get('imageId'), this.get('zarr'), this.get('theZ'), this.get('theT'), this.get('channels'), rect, targetSize);
},

get_img_src: async function(force_no_padding) {
// async function since zarr src is the rendered image data
if (this.get("zarr")) {
// use current size to choose resolution level...
let targetSize = 4 * Math.max(this.get('width'), this.get('height'));
targetSize = Math.max(targetSize, 500);
return this.get_zarr_img_src(force_no_padding, targetSize);
}
var chs = this.get('channels');
var cStrings = chs.map(function(c, i){
return (c.active ? '' : '-') + (1+i) + "|" + c.window.start + ":" + c.window.end + "$" + c.color;
Expand Down Expand Up @@ -1500,7 +1527,11 @@

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